如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
当前回答
假设你有这样的代码:
public void method() {
CompletableFuture.runAsync(() -> {
//logic
//logic
//logic
//logic
});
}
试着把它重构成这样:
public void refactoredMethod() {
CompletableFuture.runAsync(this::subMethod);
}
private void subMethod() {
//logic
//logic
//logic
//logic
}
之后,用下面的方法测试subMethod:
org.powermock.reflect.Whitebox.invokeMethod(classInstance, "subMethod");
这不是一个完美的解决方案,但它测试了异步执行中的所有逻辑。
其他回答
尽可能避免使用并行线程进行测试(大多数时候都是这样)。这只会使您的测试不可靠(有时通过,有时失败)。
只有当你需要调用其他库/系统时,你可能不得不等待其他线程,在这种情况下,总是使用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实现的方法参数,通常它将是您将模拟的注入引用。
启动进程并使用Future等待结果。
您可以尝试使用await库。这使得测试您所谈论的系统变得很容易。
我找到一个库套接字。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来阻塞直到得到结果,就像同步方式一样。并设置超时,以避免假设有太多时间等待结果。
我更喜欢使用等待和通知。它简单明了。
@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。