Java 使用EasyMock测试多线程(CompletableFuture)

Java 使用EasyMock测试多线程(CompletableFuture),java,multithreading,easymock,Java,Multithreading,Easymock,我想为包含CompletableFuture的方法添加测试: public void report(List<String> srcList) { if (srcList != null) { ... CompletableFuture.runAsync(() -> .... srcList.forEach(src-> downloader.sen

我想为包含CompletableFuture的方法添加测试:

 public void report(List<String> srcList) {
        if (srcList != null) {
            ...
            CompletableFuture.runAsync(() ->
               ....
               srcList.forEach(src-> downloader.send(url)));
        }
 }
我得到这样的错误
Downloader.send(“http://xxxx//“”:预计为2,实际为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  
    }

}
类SyncronousService实现Executor服务{
//方法空实现
公共作废提交(可运行可运行){
runnable.run();//立即运行
}
//方法空实现
}
公共类服务测试{
public void shouldPassAllItemsToDownloader()应为空{
Downloader mockDownloader=AnyMockFramework.mockIt();
服务=新服务(mockDownloader,newsycnronousservice());
List tasks=Arrays.asList(“A”、“B”);
服务工作(任务);
验证(mockDownloader)。下载(“A”);//使用EasyMock以您的方式验证
验证(mockDownloader)。下载(“B”);//使用EasyMock以您的方式验证
//不再使用Timer.sleep(),测试将立即运行
}
}
您需要将
CompletableFuture
替换为我的示例中的内容,因为 单元测试此代码不能以这种方式进行。
稍后在应用程序中,您将能够将
SyncronousService
替换为异步实现,所有这些都将按预期工作

使用
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  
    }

}
类SyncronousService实现Executor服务{
//方法空实现
公共作废提交(可运行可运行){
runnable.run();//立即运行
}
//方法空实现
}
公共类服务测试{
public void shouldPassAllItemsToDownloader()应为空{
Downloader mockDownloader=AnyMockFramework.mockIt();
服务=新服务(mockDownloader,newsycnronousservice());
List tasks=Arrays.asList(“A”、“B”);
服务工作(任务);
验证(mockDownloader)。下载(“A”);//使用EasyMock以您的方式验证
验证(mockDownloader)。下载(“B”);//使用EasyMock以您的方式验证
//不再使用Timer.sleep(),测试将立即运行
}
}
您需要将
CompletableFuture
替换为我的示例中的内容,因为 单元测试此代码不能以这种方式进行。
稍后在应用程序中,您将能够将
SyncronousService
替换为异步实现,所有这些都将按预期工作

我同意@joy dir的回答。你可能应该照她说的做,简化你的测试

为了完整性起见,这里的问题是在任务实际完成之前调用了
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);
}
最后,还有一种不切实际的方式。您可以询问公共池是否已完成。它是黑客的,因为其他东西可能会使用它。所以它很可爱,但我不推荐它

@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);
}
@测试
public void test3()引发异常{
列表事件=新建ArrayList();
事件。添加(“http://xxxx//");
事件。添加('http://xxxx//");
expect(downloader.send(events.get(0)).andReturn(“xxx”).times(2);
重播(下载);
报告(事件);
而(!ForkJoinPool.commonPool().isquisite()){
睡眠(10);
}
验证(下载程序);
}

我同意@joy dir的回答。你可能应该照她说的做,简化你的测试

为了完整性起见,这里的问题是在任务实际完成之前调用了
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);
}
最后,还有一种不切实际的方式。您可以询问公共池是否已完成。它是黑客的,因为其他东西可能会使用它。所以它很可爱,但我不推荐它

@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);
}
@测试
public void test3()引发异常{
列表事件=新建ArrayList();
事件。添加(“http://xxxx//");
事件。添加(“http://xxxx//");
expect(downloader.send)(events.get(