|
|
豪爽的香烟 · concurrent.futures -- ...· 3 月前 · |
|
|
绅士的炒饭 · jquery数据遍历动态添加到table_j ...· 1 年前 · |
|
|
乐观的炒饭 · pycharm导入talib的方法_pych ...· 2 年前 · |
|
|
无聊的莴苣 · 如何删除SQL ...· 2 年前 · |
|
|
叛逆的键盘 · javax.management.Insta ...· 2 年前 · |
|
|
含蓄的鞭炮 · Linux 下利用 gdb 查看 C++ ...· 2 年前 · |
如何使用JUnit测试触发异步流程的方法?
我不知道如何让我的测试等待过程结束(它不完全是单元测试,它更像是集成测试,因为它涉及多个类,而不只是一个类)。
启动该进程,并使用
Future
等待结果。
您可以尝试使用 Awaitility 库。它使得测试您正在谈论的系统变得很容易。
我发现测试异步方法非常有用的一种方法是在object-to-test的构造函数中注入一个
Executor
实例。在生产环境中,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
,而在测试中,我可能会使用执行以下操作的同步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());
}
如果使用 CompletableFuture (在Java8中引入)或 SettableFuture (来自 Google Guava ),则可以在测试完成时立即完成测试,而不是等待预先设置的时间。您的测试将如下所示:
CompletableFuture<String> future = new CompletableFuture<>();
executorService.submit(new Runnable() {
@Override
public void run() {
future.complete("Hello World!");
assertEquals("Hello World!", future.get());
我更喜欢使用等待和通知。它简单明了。
@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];
}
基本上,我们需要创建一个最终的数组引用,在匿名内部类中使用。我更愿意创建一个boolean[],因为我可以设置一个值来控制我们是否需要等待()。当一切都完成后,我们只需要释放asyncExecuted。
如果你想测试逻辑,就不要异步测试它。
例如,为了测试这段代码,它处理异步方法的结果。
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
}
在测试中,使用同步实现模拟依赖关系。单元测试是完全同步的,运行时间为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")));
}
您不测试异步行为,但您可以测试逻辑是否正确。
如果测试结果是异步产生的,这就是我现在使用的。
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());
}
使用静态导入,测试读起来不错。(注意,在这个例子中,我启动了一个线程来说明这个想法)
@Test
public void testAsync() {
String result = await(f -> {
new Thread(() -> f.complete("My Result")).start();
assertEquals("My Result", result);
}
如果没有调用
f.complete
,测试将在超时后失败。您还可以使用
f.completeExceptionally
提早失败。
这里有很多答案,但一个简单的方法就是创建一个完整的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都完成了!
我找到了一个库 socket.io 来测试异步逻辑。使用 LinkedBlockingQueue 看起来简单明了。这是 example
@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 take接口阻塞,直到得到结果,就像同步方式一样。并设置超时,以避免假设等待结果的时间过长。
值得一提的是,
Concurrency in Practice
中有一个非常有用的章节
Testing Concurrent Programs
,它描述了一些单元测试方法,并给出了问题的解决方案。
尽可能避免使用并行线程进行测试(大多数时候是这样)。这只会使您的测试变得不稳定(有时通过,有时失败)。
只有当您需要调用其他库/系统时,才可能需要等待其他线程,在这种情况下,请始终使用
Awaitility
库而不是
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来测试结果,你还应该通过调用
join()
或
get()
来确保你的方法
doSomething()
不会阻塞。如果您使用非阻塞框架,这一点尤其重要。
为此,请使用您设置为手动完成的未完成将来进行测试:
@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,比如在
thenApplyAsync(..., executorService)
中,那么在您的测试中注入一个单线程的ExecutorService,比如来自芭乐的那个:
ExecutorService executorService = Executors.newSingleThreadExecutor();
如果您的代码使用
thenApplyAsync(...)
等forkJoinPool,请重写代码以使用ExecutorService (有很多好的理由),或者使用Awaitility。
为了缩短示例,我在测试中将BarService设置为一个实现为Java8λ的方法参数,通常它是一个注入引用,您可以模拟它。