如何使用Junit来testingasynchronous进程
如何testing使用Junit激发asynchronous进程的方法?
我不知道如何让我的testing等待进程结束(这不完全是一个unit testing,它更像是一个集成testing,因为它涉及几个类而不只是一个)
恕我直言,不好的做法是让unit testing在线程上创build或等待,等等。你希望这些testing在分秒内运行。 这就是为什么我想提出一个testingasynchronousstream程的两步法。
- testing你的asynchronous过程是否正确提交。 您可以模拟接受asynchronous请求的对象,并确保提交的作业具有正确的属性等。
- testing你的asynchronouscallback正在做正确的事情。 在这里,您可以嘲笑最初提交的作业,并假设它已正确初始化,并validation您的callback是否正确。
另一种方法是使用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()); } }
注意,你不能仅仅使用与普通对象同步的方式作为锁,因为快速callback可以在锁的等待方法被调用之前释放锁。 请参阅Joe Walnes的博客文章。
编辑删除了CountDownLatch周围的同步块,感谢来自@jtahlborn和@Ring的评论
您可以尝试使用Awaitility库。 它可以很容易地testing你正在谈论的系统。
如果您使用CompletableFuture (在Java 8中引入)或SettableFuture (来自Google Guava ),则可以在完成testing后立即完成testing,而不是等待预设的时间。 你的testing看起来像这样:
CompletableFuture<String> future = new CompletableFuture<>(); executorService.submit(new Runnable() { @Override public void run() { future.complete("Hello World!"); } }); assertEquals("Hello World!", future.get());
启动该过程,并使用Future
等待结果。
我发现的一个方法对于testingasynchronous方法非常有用,是在对象testing的构造函数中注入一个Executor
实例。 在生产中,执行程序实例被configuration为asynchronous运行,而在testing中它可以被模拟为同步运行。
所以假设我试图testingasynchronous方法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
,而在testing中,我可能会用一个同步执行程序构造它,
class SynchronousExecutor implements Executor { @Override public void execute(Runnable r) { r.run(); } }
现在我的JUnitasynchronous方法的testing很干净 –
@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()); }
如何调用SomeObject.wait
和notifyAll
SomeObject.wait
或使用Robotiums Solo.waitForCondition(...)
方法,或者使用我写的类来完成此操作(请参阅注释和testing类以了解如何使用)
testing线程/asynchronous代码没有任何内在的错误,特别是如果线程是您正在testing的代码的重点 。 testing这个东西的一般方法是:
- 阻止主testing线程
- 从其他线程捕获失败的断言
- 解锁主testing线程
- 反思任何失败
但这是一个testing的很多样板。 更好/更简单的方法是使用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
方法相比,这样做的好处在于它不那么冗长,因为在任何线程中发生的断言失败都会正确地报告给主线程,这意味着testing在应该执行时会失败。 将CountdownLatch
方法与ConcurrentUnit进行比较的CountdownLatch
就在这里 。
我还为那些想要了解更多细节的人写了一篇博文 。
我更喜欢使用等待和通知。 这很简单明了。
@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]; } }
基本上,我们需要创build一个最终的数组引用,用于匿名内部类的内部。 我宁愿创build一个布尔值[],因为如果需要等待(),我可以把一个值来控制。 当一切完成后,我们只是释放asynchronous执行。
我find一个库socket.io来testingasynchronous逻辑。 它看起来简单而简单,使用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阻塞,直到得到如同步方式的结果。 并设置超时,以避免假设太多的时间来等待结果。
这里有很多答案,但最简单的就是创build一个完整的CompletableFuture并使用它:
CompletableFuture.completedFuture("donzo")
所以在我的testing中:
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都完成后,它将通过它正确的拉链!
如果你想testing逻辑只是不要asynchronoustesting。
例如,要testing这个代码对asynchronous方法的结果起作用。
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 } }
在testing模拟与同步实现的依赖。 unit testing完全同步,运行时间为150ms。
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"))); } }
您不testingasynchronous行为,但可以testing逻辑是否正确。
这是我现在使用的testing结果是asynchronous产生的。
public class TestUtil { public static <R> R await(Consumer<CompletableFuture<R>> completer) { return await(20, TimeUnit.SECONDS, completer); } public static <R> R await(int time, TimeUnit unit, Consumer<CompletableFuture<R>> completer) { CompletableFuture<R> f = new CompletableFuture<>(); completer.accept(f); try { return f.get(time, unit); } catch (InterruptedException | TimeoutException e) { throw new RuntimeException("Future timed out", e); } catch (ExecutionException e) { throw new RuntimeException("Future failed", e.getCause()); } } }
使用静态导入,testing读起来不错。 (注意,在这个例子中,我开始一个线程来说明这个想法)
@Test public void testAsync() { String result = await(f -> { new Thread(() -> f.complete("My Result")).start(); }); assertEquals("My Result", result); }
如果f.complete
没有被调用,testing将在超时后失败。 您也可以使用f.completeExceptionally
失败。
对于那些喜欢学习的人,我认为这是一个很好的: https : //github.com/playframework/play-java-ebean-example/blob/2.6.x/test/ModelTest.java
值得一提的是,在并发实践中有非常有用的章节“ Testing Concurrent Programs
,它描述了一些unit testing方法并给出了问题的解决scheme。