如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
当前回答
如果你想测试逻辑,不要异步测试。
例如,测试这段代码,它对异步方法的结果起作用。
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")));
}
}
你不测试异步行为,但你可以测试逻辑是否正确。
其他回答
对于测试异步方法,我发现一个非常有用的方法是在测试对象的构造函数中注入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());
}
如果你想测试逻辑,不要异步测试。
例如,测试这段代码,它对异步方法的结果起作用。
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")));
}
}
你不测试异步行为,但你可以测试逻辑是否正确。
值得一提的是,《并发实践》中有一章非常有用,它描述了一些单元测试方法,并给出了解决问题的方案。
这里有很多答案,但一个简单的答案是创建一个完整的CompletableFuture并使用它:
CompletableFuture.completedFuture("donzo")
所以在我的测试中:
this.exactly(2).of(mockEventHubClientWrapper).sendASync(with(any(LinkedList.class)));
this.will(returnValue(new CompletableFuture<>().completedFuture("donzo")));
我只是确保所有这些东西都会被调用。如果你使用下面的代码,这个技巧是有效的:
CompletableFuture.allOf(calls.toArray(new CompletableFuture[0])).join();
它将压缩通过它,因为所有的CompletableFutures都完成了!
您可以尝试使用await库。这使得测试您所谈论的系统变得很容易。