2017-06-22 51 views
0

我想补充试验方法,该方法包含CompletableFuture:测试多线程(CompletableFuture)与EasyMock的

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测试多线程?

回答

3

这是一个不好的做法,单元测试asynchronoys代码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

我同意@ 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); 
}