如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
当前回答
如果您使用CompletableFuture(在Java 8中引入)或SettableFuture(来自谷歌Guava),您可以在测试完成后立即完成测试,而不是等待预先设置的时间。你的测试应该是这样的:
CompletableFuture<String> future = new CompletableFuture<>();
executorService.submit(new Runnable() {
@Override
public void run() {
future.complete("Hello World!");
}
});
assertEquals("Hello World!", future.get());
注意,有一个库为pre Java-8提供了CompletableFuture,它甚至使用了相同的名称(并提供了所有相关的Java-8类),例如: net.sourceforge.streamsupport: streamsupport-minifuture: 1.7.4 这对于Android开发很有用,即使我们使用JDK-v11构建,我们也希望保持代码与前Android-7设备兼容。
其他回答
TL,博士;不幸的是,目前还没有内置的解决方案(在撰写本文时,2022年),因此您可以自由使用和/或实现任何适合您的情况。
例子
另一种方法是使用CountDownLatch类。
public class DatabaseTest {
/**
* Data limit
*/
private static final int DATA_LIMIT = 5;
/**
* Countdown latch
*/
private CountDownLatch lock = new CountDownLatch(1);
/**
* Received data
*/
private List<Data> receiveddata;
@Test
public void testDataRetrieval() throws Exception {
Database db = new MockDatabaseImpl();
db.getData(DATA_LIMIT, new DataCallback() {
@Override
public void onSuccess(List<Data> data) {
receiveddata = data;
lock.countDown();
}
});
lock.await(2000, TimeUnit.MILLISECONDS);
assertNotNull(receiveddata);
assertEquals(DATA_LIMIT, receiveddata.size());
}
}
注意:不能只使用与常规对象同步的对象作为锁,因为快速回调可以在锁的wait方法被调用之前释放锁。请参阅Joe Walnes的博客文章。
由于@jtahlborn和@Ring的评论,删除了CountDownLatch周围的同步块
恕我直言,让单元测试创建或等待线程是一种糟糕的实践。您希望这些测试在瞬间运行。这就是为什么我想提出一个测试异步进程的两步方法。
测试您的异步进程是否正确提交。您可以模拟接受异步请求的对象,并确保提交的作业具有正确的属性,等等。 测试你的异步回调是否在做正确的事情。在这里,您可以模拟最初提交的作业,并假设它已正确初始化,并验证您的回调是否正确。
如果您使用CompletableFuture(在Java 8中引入)或SettableFuture(来自谷歌Guava),您可以在测试完成后立即完成测试,而不是等待预先设置的时间。你的测试应该是这样的:
CompletableFuture<String> future = new CompletableFuture<>();
executorService.submit(new Runnable() {
@Override
public void run() {
future.complete("Hello World!");
}
});
assertEquals("Hello World!", future.get());
注意,有一个库为pre Java-8提供了CompletableFuture,它甚至使用了相同的名称(并提供了所有相关的Java-8类),例如: net.sourceforge.streamsupport: streamsupport-minifuture: 1.7.4 这对于Android开发很有用,即使我们使用JDK-v11构建,我们也希望保持代码与前Android-7设备兼容。
如果你想测试逻辑,不要异步测试。
例如,测试这段代码,它对异步方法的结果起作用。
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")));
}
}
你不测试异步行为,但你可以测试逻辑是否正确。
调用SomeObject怎么样?或者使用Robotiums Solo.waitForCondition(…)方法,或者使用我写的一个类来做到这一点(如何使用,请参阅注释和测试类)