[英]test multithreading (CompletableFuture) with EasyMock
我想為方法添加測試,其中包含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測試多線程嗎?
用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
替換為異步實現,並且所有操作都將按預期進行。
我同意@ 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);
}
成功的話,它不會長時間無休止的睡覺。 但是,您確實需要確保等待足夠長的時間以防止測試不穩定。
另一個解決方案實際上是使用runAsync
返回的CompletableFuture
。 我更喜歡這種解決方案。
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);
}
最后,有一種破解方法。 您詢問公用池是否已完成。 這是駭客的,因為其他東西可能會使用它。 所以它很可愛,但我不會推薦它。
@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);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.