我想为方法添加测试,其中包含CompletableFuture:
public void report(List<String> srcList) {
if (srcList != null) {
...
CompletableFuture.runAsync(() ->
....
srcList.forEach(src-> downloader.send(url)));
}
}
我想测试一下,调用方法send
。我的测试看起来像:
@Test
public void _test() {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
eventReporter.report(events);
verify(downloader);
}
我收到了这样的错误Downloader.send("http://xxxx//"): expected: 2, actual: 0
避免此错误的一种方法是设置Thread.sleep(100);
超时。然后线程将等待并验证该方法是否已调用。但这会增加测试时间。
还有其他方法可以使用EasyMock测试多线程吗?
答案 0 :(得分:3)
使用Thread.sleep()
方法对异步代码进行单元测试是一种不好的做法
因为如果它甚至工作测试将是不稳定和闪烁(运行3次2次通过,1次失败)
如果你设置了很长的睡眠时间并写下这样的测试,你就会遇到很大的执行时间
这可能会超过几十秒。要完成此任务,您需要解耦异步部分
你的同步代码。示例如何操作:
class Service {
private Downloader downloader;
private ExecutorService service;
public Service (Downloader downloader, ExecutorService service) {
//set variables
}
public void doWork(List<String> list) {
for (String item : list) {
service.submit(() -> {
downloader.download(item);
});
}
}
}
ExecutorService是接口,我们需要使我们的服务同步
class SycnronousService impliments ExecutorService {
//methods empty implementations
public void submit(Runnable runnable) {
runnable.run(); //run immediately
}
//methods empty implementations
}
public class ServiceTest {
public void shouldPassAllItemsToDownloader() {
Downloader mockDownloader = AnyMockFramework.mockIt();
Service service = new Service(mockDownloader, new SycnronousService());
List<String> tasks = Arrays.asList("A", "B");
service.doWork(tasks);
verify(mockDownloader).download("A"); //verify in your way with EasyMock
verify(mockDownloader).download("B"); //verify in your way with EasyMock
// no more Timer.sleep() , test runs immeadetely
}
}
您需要将CompletableFuture
替换为我的示例中的内容,因为
单元测试此代码不能以这种方式。
稍后在您的应用程序中,您将能够将SycnronousService
替换为异步实现,并且所有操作都将按预期工作。
答案 1 :(得分:1)
我同意@ joy-dir的回答。你可能应该按照她的说法简化你的测试。
为了完整起见,您的问题是在您的任务实际完成之前调用verify
。你可以做很多事情。
一个是循环verify
。
@Test
public void test() throws Exception {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
report(events);
for (int i = 0; i < 10; i++) {
try {
verify(downloader);
return;
} catch(AssertionError e) {
// wait until it works
}
Thread.sleep(10);
}
verify(downloader);
}
成功时不会长时间无所事事。但是,您确实需要确保等待足以防止测试变薄。
另一种解决方案实际上是使用CompletableFuture
返回的runAsync
。我更喜欢这个解决方案。
public CompletableFuture<Void> report(List<String> srcList) {
if (srcList != null) {
return CompletableFuture.runAsync(() -> srcList.forEach(src-> downloader.send(src)));
}
return CompletableFuture.completedFuture(null);
}
@Test
public void test2() throws Exception {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
CompletableFuture<Void> future = report(events);
future.get(100, TimeUnit.MILLISECONDS);
verify(downloader);
}
最后,有一种hackish方式。你问公共池是否完成。它是hackish,因为其他东西可能会使用它。所以它很可爱,但我不会真的推荐它。
@Test
public void test3() throws Exception {
List<String> events = new ArrayList();
events.add("http://xxxx//");
events.add("http://xxxx//");
expect(downloader.send(events.get(0))).andReturn("xxx").times(2);
replay(downloader);
report(events);
while(!ForkJoinPool.commonPool().isQuiescent()) {
Thread.sleep(10);
}
verify(downloader);
}