我有以异步方式调用
connector.runSomeService(data)
并处理方法handleServiceResponse(res, node)
中的响应的方法。
public void runServiceOnAllNodes(Collection<Node> nodes, Object data) {
nodes.parallelStream().forEach(node -> {
CompletableFuture<ResponseEntity> response = CompletableFuture
.supplyAsync(()-> connector.runSomeService(data));
response.exceptionally(ex -> {
log.error("OMG...OMG!!!")
return null;
})
.thenAcceptAsync(res -> handleServiceResponse(res, node));
});
}
private void handleServiceResponse(ResponseEntity res, Node node) {
if (res.isOK) {
node.setOKStatus();
} else {
node.setFailStatus();
}
dbService.saveNode(node);
}
尝试创建单元测试,但是当我尝试验证响应是否得到正确处理时,UT 的结果是不确定的。
@Test
public void testRunServiceOnAllNodes() {
// given
List<Collector> nodes = Arrays.asList(node1, node2, node3);
when(connector.runSomeService(eq(node1), eq(data))).thenReturn(ResponseEntity.ok().body("{message:OK}"));
when(connector.runSomeService(eq(node2), eq(data))).thenReturn(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""));
when(connector.runSomeService(eq(node3), eq(data))).thenThrow(new ResourceAccessException(""));
// when
engine.runServiceOnAllNodes(data, collectors);
// then
verify(connector, times(1)).runSomeService(eq(node1), eq(data));
verify(connector, times(1)).runSomeService(eq(node2), eq(data));
verify(connector, times(1)).runSomeService(eq(node3), eq(data));
verifyNoMoreInteractions(connector);
assertEquals(node1.getStatus(), "OK");
assertEquals(node2.getStatus(), "Fail");
}
它可以以一些不同的结果结束,例如。
Wanted but not invoked:
connector.runSomeService(node2);
However, there were other interactions with this mock:
connector.runSomeService(node1);
或
Argument(s) are different! Wanted:
connector.runSomeService(node1);
Actual invocation has different arguments:
connector.deployFileset(node2);
有时它以成功结束。
很明显,执行的时间
connector.runSomeService()
和验证的时间可以交错。这两个动作的顺序是不确定的。
使用睡眠很糟糕。试图收集所有响应并调用 future.get()
// when
engine.runServiceOnAllNodes(data, collectors);
for (CompletableFuture future : engine.getResponses()) {
future.get();
}
但是我遇到了一些异常,但我仍然觉得这种方式也很糟糕,不是吗?
我建议更改
runServiceOnAllNodes
方法以返回 Future
这样您的测试以及作为奖励的普通客户端都可以明确等待异步行为完成。
public Future<Void> runServiceOnAllNodes(Collection<Node> nodes, Object data) {
return nodes.parallelStream().map(node -> {
CompletableFuture<ResponseEntity> response = CompletableFuture
.supplyAsync(()-> connector.runSomeService(data));
return response.exceptionally(ex -> {
LOGGER.error("OMG...OMG!!!");
return null;
})
.thenAcceptAsync(res -> handleServiceResponse(res, node));
})
.reduce(CompletableFuture::allOf).orElseGet(() -> CompletableFuture.completedFuture(null));
}
在您的测试中,这只是在进行断言和验证之前调用
get()
的问题。
我有一个单元测试场景,其中在方法内部使用 CompletableFuture,然后调用 .get() 来检索值。像这样的东西:
public class SomeService {
public Object getData(String id) {
..some code...
CompletableFuture taskToComplete = CompletableFuture.supplyAsync(task, executor);
..some code...
Data data = taskToComplete.get();
..some code...
}
}
在我的测试课上,我遵循了以下步骤:
创建了一个 CompletableFuture 的模拟对象,就像这样。
@Mock
CompletableFuture<T> detailsTask;
分配完成的响应值,比如
@Test
void testGetDetails() {
detailsTask = CompletableFuture.completedFuture(response);
}
最后,模拟静态 supplyAsync 方法。
completableFutureMock.when(() -> CompletableFuture.supplyAsync(any(), any(Executor.class))).thenReturn(detailsTask);
最终的变化看起来像这样:
class Test {
@Mock
CompletableFuture<T> detailsTask;
@Test
void testGetDetails() {
detailsTask = CompletableFuture.completedFuture(data);
try (MockedStatic<CompletableFuture> completableFutureMock = Mockito.mockStatic(CompletableFuture.class)) {
completableFutureMock.when(() -> CompletableFuture.supplyAsync(any(), any(Executor.class))).thenReturn(detailsTask);
}
}
}