如何测试使用Junit激发异步进程的方法?
我不知道如何让我的测试等待进程结束(它不是一个单元测试,它更像是一个集成测试,因为它涉及几个类而不仅仅是一个)
另一种方法是使用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 receiveddata; @Test public void testDataRetrieval() throws Exception { Database db = new MockDatabaseImpl(); db.getData(DATA_LIMIT, new DataCallback() { @Override public void onSuccess(List data) { receiveddata = data; lock.countDown(); } }); lock.await(2000, TimeUnit.MILLISECONDS); assertNotNull(receiveddata); assertEquals(DATA_LIMIT, receiveddata.size()); } }
注意你不能只使用与常规对象同步作为锁,因为快速回调可以在调用lock的wait方法之前释放锁.请参阅Joe Walnes撰写的这篇博客文章.
EDIT删除了CountDownLatch周围的同步块,感谢@jtahlborn和@Ring的评论
您可以尝试使用Awaitility库.它可以轻松测试您正在谈论的系统.
如果您使用CompletableFuture(在Java 8中引入)或SettableFuture(来自Google Guava),您可以在完成后立即完成测试,而不是等待预先设定的时间.您的测试看起来像这样:
CompletableFuturefuture = new CompletableFuture<>(); executorService.submit(new Runnable() { @Override public void run() { future.complete("Hello World!"); } }); assertEquals("Hello World!", future.get());
恕我直言,让单元测试创建或等待线程等是不好的做法.你希望这些测试在几秒钟内运行.这就是为什么我想提出一个两步测试异步过程的方法.
测试您的异步过程是否正确提交.您可以模拟接受异步请求的对象,并确保提交的作业具有正确的属性等.
测试你的异步回调正在做正确的事情.在这里,您可以模拟最初提交的作业并假设它已正确初始化并验证您的回调是否正确.
关闭该过程并使用a等待结果Future
.
我发现一种测试异步方法非常有用的方法是Executor
在object-to-test的构造函数中注入一个实例.在生产中,执行程序实例配置为异步运行,而在测试中,它可以被模拟为同步运行.
所以假设我正在尝试测试异步方法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); } }); } }
在生产中,我将Foo
使用Executors.newSingleThreadExecutor()
Executor实例构建,而在测试中,我可能会使用执行以下操作的同步执行程序构造它 -
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()); }
测试线程/异步代码没有任何内在错误,特别是如果线程是您正在测试的代码的重点.测试这些东西的一般方法是:
阻止主测试线程
从其他线程捕获失败的断言
取消阻止主测试线程
重新抛出任何失败
但这是一次测试的很多样板.更好/更简单的方法是使用ConcurrentUnit:
final Waiter waiter = new Waiter(); new Thread(() -> { doSomeWork(); waiter.assertTrue(true); waiter.resume(); }).start(); // Wait for resume() to be called waiter.await(1000);
这种CountdownLatch
方法的好处在于,它更简洁,因为任何线程中发生的断言失败都被正确地报告给主线程,这意味着测试失败了.这里比较了CountdownLatch
ConcurrentUnit 的方法.
我还为那些想要学习更多细节的人写了一篇关于这个主题的博客文章.