使用CountDownLatch或循环屏障对多线程代码进行单元测试 -Xebia


随着处理器比以往包装更多的内核,并发编程已成为最有效利用它们的最前沿。但是,事实是并发程序的设计,编写,测试和维护要困难得多。因此,如果我们毕竟可以为并发程序编写有效且自动化的测试用例,则可以解决其中的大部分问题。

CountDownLatch

@Test
public void should_publish_an_article_using_count_down_latch_to_fix() throws InterruptedException {
    //Arrange
    Article article = Article.newBuilder()
            .withBody(
"learning how to test multithreaded java code")
            .withId(1)
            .withTitle(
"title").build();

    CountDownLatch countDownLatch = new CountDownLatch(1);
    when(this.articleRepository.findById(1)).thenReturn(article);
    doAnswer(invocationOnMock -> {
        System.out.println(
"Sending mail !!!");
        countDownLatch.countDown();
        return null;
    }).when(this.emailSender).sendEmail(anyString(), anyString());

   
//Act
    boolean publish = this.articlePublisher.publish(1);

   
//Assert
    assertThat(publish).isTrue();
    verify(this.articleRepository).findById(1);
    countDownLatch.await();
    verify(this.emailSender).sendEmail(
"Article Published With Id " + 1
            ,
"Published an article with Article Title " + "title");
    verifyNoMoreInteractions(this.articleRepository, this.emailSender);
}

我们使用CountDownLatch,以便主线程应等待,直到调用send email方法。我们调用countDownLatch的countDown方法进行暂停倒计时,直至awat()方法唤醒。

使用循环屏障Cyclic Barrier

@Test
public void should_publish_an_article_using_cyclic_barrier_to_fix() throws BrokenBarrierException, InterruptedException {
    //Arrange
    Article article = Article.newBuilder()
            .withBody(
"learning how to test multithreaded java code")
            .withId(1)
            .withTitle(
"title").build();

    CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> System.out.println(
"Barrier opening"));
    when(this.articleRepository.findById(1)).thenReturn(article);
    doAnswer(invocationOnMock -> {
        System.out.println(
"sending mail !!!");
        cyclicBarrier.await();
        return null;
    }).when(this.emailSender).sendEmail(anyString(), anyString());
   
//Act
    boolean publish = this.articlePublisher.publish(1);

   
//Assert
    assertThat(publish).isTrue();
    verify(this.articleRepository).findById(1);
    cyclicBarrier.await();
    verify(this.emailSender).sendEmail(
"Article Published With Id " + 1
            ,
"Published an article with Article Title " + "title");
    verifyNoMoreInteractions(this.articleRepository, this.emailSender);
}

循环屏障使这两个并发任务同步进行。当emailSender线程的sendEmail方法和主线程同步时,将打开屏障。

随着线程数量的增加,它们可能交错的方式也呈指数增长。根本不可能弄清楚所有这样的交错并对其进行测试。我们必须依靠工具为我们进行相同或相似的工作。幸运的是,其中有一些工具可以使我们的生活更轻松。