【问题标题】:test multithreading (CompletableFuture) with EasyMock使用 EasyMock 测试多线程(CompletableFuture)
【发布时间】:2017-06-22 17:25:28
【问题描述】:

我想为包含 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 测试多线程吗?

【问题讨论】:

    标签: java multithreading easymock


    【解决方案1】:

    使用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 替换为异步实现,并且一切都将按预期工作。

    【讨论】:

      【解决方案2】:

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

      最后,有一种骇人听闻的方式。你问公共池是否完成。这是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);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-03
        • 1970-01-01
        • 2011-11-26
        • 1970-01-01
        • 2023-03-24
        • 1970-01-01
        相关资源
        最近更新 更多