如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
当前回答
我更喜欢使用等待和通知。它简单明了。
@Test
public void test() throws Throwable {
final boolean[] asyncExecuted = {false};
final Throwable[] asyncThrowable= {null};
// do anything async
new Thread(new Runnable() {
@Override
public void run() {
try {
// Put your test here.
fail();
}
// lets inform the test thread that there is an error.
catch (Throwable throwable){
asyncThrowable[0] = throwable;
}
// ensure to release asyncExecuted in case of error.
finally {
synchronized (asyncExecuted){
asyncExecuted[0] = true;
asyncExecuted.notify();
}
}
}
}).start();
// Waiting for the test is complete
synchronized (asyncExecuted){
while(!asyncExecuted[0]){
asyncExecuted.wait();
}
}
// get any async error, including exceptions and assertationErrors
if(asyncThrowable[0] != null){
throw asyncThrowable[0];
}
}
基本上,我们需要创建一个最终的数组引用,在匿名内部类中使用。我宁愿创建一个布尔[],因为如果我们需要wait(),我可以设置一个值来控制。当一切都完成后,我们只需释放asyncExecuted。
其他回答
我找到一个库套接字。IO来测试异步逻辑。使用LinkedBlockingQueue看起来简单。这里有一个例子:
@Test(timeout = TIMEOUT)
public void message() throws URISyntaxException, InterruptedException {
final BlockingQueue<Object> values = new LinkedBlockingQueue<Object>();
socket = client();
socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
@Override
public void call(Object... objects) {
socket.send("foo", "bar");
}
}).on(Socket.EVENT_MESSAGE, new Emitter.Listener() {
@Override
public void call(Object... args) {
values.offer(args);
}
});
socket.connect();
assertThat((Object[])values.take(), is(new Object[] {"hello client"}));
assertThat((Object[])values.take(), is(new Object[] {"foo", "bar"}));
socket.disconnect();
}
使用LinkedBlockingQueue使用API来阻塞直到得到结果,就像同步方式一样。并设置超时,以避免假设有太多时间等待结果。
对于测试异步方法,我发现一个非常有用的方法是在测试对象的构造函数中注入Executor实例。在生产中,执行器实例被配置为异步运行,而在测试中,它可以被模拟为同步运行。
所以假设我试图测试异步方法Foo#doAsync(Callback c),
class Foo {
private final Executor executor;
public Foo(Executor executor) {
this.executor = executor;
}
public void doAsync(Callback c) {
executor.execute(new Runnable() {
@Override public void run() {
// Do stuff here
c.onComplete(data);
}
});
}
}
在生产中,我会用Executors.newSingleThreadExecutor() Executor实例构造Foo,而在测试中,我可能会用执行以下操作的同步执行器构造它
class SynchronousExecutor implements Executor {
@Override public void execute(Runnable r) {
r.run();
}
}
现在异步方法的JUnit测试非常干净
@Test public void testDoAsync() {
Executor executor = new SynchronousExecutor();
Foo objectToTest = new Foo(executor);
Callback callback = mock(Callback.class);
objectToTest.doAsync(callback);
// Verify that Callback#onComplete was called using Mockito.
verify(callback).onComplete(any(Data.class));
// Assert that we got back the data that we expected.
assertEquals(expectedData, callback.getData());
}
尽可能避免使用并行线程进行测试(大多数时候都是这样)。这只会使您的测试不可靠(有时通过,有时失败)。
只有当你需要调用其他库/系统时,你可能不得不等待其他线程,在这种情况下,总是使用await库而不是Thread.sleep()。
永远不要在测试中只调用get()或join(),否则您的测试可能会在CI服务器上一直运行,以防将来永远无法完成。在调用get()之前,始终在测试中首先断言isDone()。对于CompletionStage,就是. tocompletablefuture (). isdone()。
当你像这样测试一个非阻塞方法时:
public static CompletionStage<String> createGreeting(CompletableFuture<String> future) {
return future.thenApply(result -> "Hello " + result);
}
那么你不应该仅仅通过在测试中传递一个完整的Future来测试结果,你还应该确保你的方法doSomething()不会通过调用join()或get()来阻塞。如果使用非阻塞框架,这一点尤其重要。
要做到这一点,测试一个未完成的未来,你手动设置为完成:
@Test
public void testDoSomething() throws Exception {
CompletableFuture<String> innerFuture = new CompletableFuture<>();
CompletableFuture<String> futureResult = createGreeting(innerFuture).toCompletableFuture();
assertFalse(futureResult.isDone());
// this triggers the future to complete
innerFuture.complete("world");
assertTrue(futureResult.isDone());
// futher asserts about fooResult here
assertEquals(futureResult.get(), "Hello world");
}
这样,如果您将future.join()添加到doSomething(),测试将失败。
如果你的服务使用ExecutorService,比如applyasync(…, executorService),然后在你的测试中注入一个单线程的executorService,比如来自guava的:
ExecutorService executorService = Executors.newSingleThreadExecutor();
如果你的代码使用forkJoinPool,比如applyasync(…),重写代码使用ExecutorService(有很多好的理由),或者使用await。
为了缩短示例,我将BarService设置为测试中作为Java8 lambda实现的方法参数,通常它将是您将模拟的注入引用。
调用SomeObject怎么样?或者使用Robotiums Solo.waitForCondition(…)方法,或者使用我写的一个类来做到这一点(如何使用,请参阅注释和测试类)
如果你想测试逻辑,不要异步测试。
例如,测试这段代码,它对异步方法的结果起作用。
public class Example {
private Dependency dependency;
public Example(Dependency dependency) {
this.dependency = dependency;
}
public CompletableFuture<String> someAsyncMethod(){
return dependency.asyncMethod()
.handle((r,ex) -> {
if(ex != null) {
return "got exception";
} else {
return r.toString();
}
});
}
}
public class Dependency {
public CompletableFuture<Integer> asyncMethod() {
// do some async stuff
}
}
在测试中模拟使用同步实现的依赖关系。单元测试是完全同步的,运行时间为150毫秒。
public class DependencyTest {
private Example sut;
private Dependency dependency;
public void setup() {
dependency = Mockito.mock(Dependency.class);;
sut = new Example(dependency);
}
@Test public void success() throws InterruptedException, ExecutionException {
when(dependency.asyncMethod()).thenReturn(CompletableFuture.completedFuture(5));
// When
CompletableFuture<String> result = sut.someAsyncMethod();
// Then
assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
String value = result.get();
assertThat(value, is(equalTo("5")));
}
@Test public void failed() throws InterruptedException, ExecutionException {
// Given
CompletableFuture<Integer> c = new CompletableFuture<Integer>();
c.completeExceptionally(new RuntimeException("failed"));
when(dependency.asyncMethod()).thenReturn(c);
// When
CompletableFuture<String> result = sut.someAsyncMethod();
// Then
assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
String value = result.get();
assertThat(value, is(equalTo("got exception")));
}
}
你不测试异步行为,但你可以测试逻辑是否正确。