【问题标题】:How to run concurrency unit test?如何运行并发单元测试?
【发布时间】:2010-11-16 15:23:01
【问题描述】:

如何使用junit进行并发测试?

假设我有一堂课

public class MessageBoard
{
    public synchronized void postMessage(String message)
    {
        ....
    }

    public void updateMessage(Long id, String message)
    {
        ....
    }
}

我想同时测试对这个 postMessage 的多重访问。 对此有何建议?我希望对我所有的 setter 函数(或任何涉及创建/更新/删除操作的方法)运行这种并发测试。

【问题讨论】:

    标签: unit-testing concurrency junit


    【解决方案1】:

    不幸的是,我不相信您可以明确通过使用运行时测试来证明您的代码是线程安全的。您可以针对它抛出任意数量的线程,并且它可能会/可能不会通过,具体取决于调度。

    也许您应该查看一些静态分析工具,例如 PMD,它们可以确定您如何使用同步并识别使用问题。

    【讨论】:

    【解决方案2】:

    您只能证明存在并发错误,而不能证明它们不存在

    但是,您可以编写一个专门的测试运行程序,它会产生多个并发线程,然后调用您的 @Test 注释方法。

    【讨论】:

    • > (通过测试)“你只能证明并发错误的存在,而不是它们的缺失。”在一般意义上,即使对于非并发错误也是如此。
    • 这有点正确,但是通过传统的单元测试,您可以证明(当前)没有特定错误。在测试并发问题时,您甚至不会得到那么多。
    【解决方案3】:

    TestNG 支持 Java 中的并发测试。这个article 描述了如何使用它,并且在 testng 网站上有文档。

    不确定是否可以同时运行相同的测试

    【讨论】:

      【解决方案4】:

      这里最好的方法是使用系统测试通过请求来爆破您的代码,看看它是否会崩溃。然后使用单元测试来检查逻辑正确性。我处理这个问题的方法是为异步调用创建一个代理,并让它在测试中同步进行。

      但是,如果您想在完整安装和完整环境之外的级别执行此操作,您可以在 junit 中通过在单独的线程中创建对象来执行此操作,然后创建许多线程来触发对您的对象的请求并阻塞主线程直到它完成。如果您不正确,这种方法可能会导致测试间歇性失败。

      【讨论】:

      • 不幸的是,这种方法在发现错误方面非常无效,因为重复运行相同的测试通常会一遍又一遍地测试相同的线程。
      • 你会怎么做呢?一套代表客户场景的综合系统测试将发现客户会看到的错误。
      【解决方案5】:

      在 .NET 中,有一些工具,例如 TypeMock RacerMicrosoft CHESS,专门用于单元测试并发性。这些工具不仅可以发现死锁等多线程错误,还可以为您提供重现错误的一组线程交错。

      我想 Java 世界也有类似的东西。

      【讨论】:

        【解决方案6】:

        在您的示例中,postMessage() 方法是同步的,因此您实际上不会在单个 VM 中看到任何并发影响,但您可能能够评估同步版本的性能。

        您需要在不同的虚拟机中同时运行测试程序的多个副本。你可以使用

        如果您无法让您的测试框架执行此操作,您可以自己启动一些虚拟机。 流程构建器的东西是路径和诸如此类的痛苦,但这是一般草图:

        Process running[] = new Process[5];
        for (int i = 0; i < 5; i++) {
         ProcessBuilder b = new ProcessBuilder("java -cp " + getCP() + " MyTestRunner");
         running[i] = b.start();
        }
        
        for(int i = 0; i < 5; i++) {
         running[i].waitFor();
        }
        

        我通常为简单的线程测试做这样的事情,就像其他人发布的那样,测试不是正确性的证明,但它通常会在实践中消除愚蠢的错误。它有助于在各种不同的条件下进行长时间的测试——有时并发错误需要一段时间才能在测试中显现出来。

        public void testMesageBoard() {
         final MessageBoard b = new MessageBoard();
        
         int n = 5;
         Thread T[] = new Thread[n];
         for (int i = 0; i < n; i++) {
          T[i] = new Thread(new Runnable() {
           public void run() {
            for (int j = 0; j < maxIterations; j++) {
              Thread.sleep( random.nextInt(50) );
              b.postMessage(generateMessage(j));
              verifyContent(j); // put some assertions here
            }
           }
          });
        
          PerfTimer.start();
          for (Thread t : T) {
           t.start();
          }
        
          for (Thread t : T) {
           t.join();
          }
          PerfTimer.stop();
          log("took: " + PerfTimer.elapsed());
         }
        }**strong text**
        

        【讨论】:

          【解决方案7】:

          测试并发错误是不可能的;您不仅需要验证输入/输出对,还必须在测试期间可能发生或不发生的情况下验证状态。不幸的是,JUnit 不具备这样做的能力。

          【讨论】:

          【解决方案8】:

          我建议使用MultithreadedTC - 由并发大师本人编写Bill Pugh(和Nat Ayewah)。引用他们的overview

          MultithreadedTC 是一个框架 测试并发应用程序。它 具有一个节拍器,用于 提供对序列的精细控制 多个线程中的活动。

          此框架允许您确定性地测试在单独测试中交错的每个线程

          【讨论】:

            【解决方案9】:

            您可以使用 tempus-fugit 库并行和多次运行测试方法来模拟负载测试类型的环境。尽管先前的评论指出 post 方法是同步的并因此受到保护,但可能涉及本身不受保护的关联成员或方法,因此加载/浸泡类型测试 可能 抓住这些。我建议你设置一个相当粗粒度/端到端的测试,让你有最好的机会发现任何循环漏洞。

            请参阅documentation 的 JUnit 集成部分。

            顺便说一句,我是上述项目的开发人员 :)

            【讨论】:

            • 我对这种测试策略的经验是,当测试失败时,没有足够的诊断信息来追踪错误,所以你最终会得到一个不规则失败的测试,而你没有想调查。 @Judah Himango 建议可以帮助解决这个问题的库。
            • 已修复(并强制输入至少 15 个字符)
            【解决方案10】:

            尝试查看 JUnit 附带的 ActiveTestSuite。它可以同时启动多个 JUnit 测试:

            public static Test suite()
            {
                TestSuite suite = new ActiveTestSuite();
                suite.addTestSuite(PostMessageTest.class);
                suite.addTestSuite(PostMessageTest.class);
                suite.addTestSuite(PostMessageTest.class);
                suite.addTestSuite(PostMessageTest.class);
                suite.addTestSuite(PostMessageTest.class);
                return suite;
            }
            

            上面将并行运行相同的 JUnit 测试类 5 次。如果您想在并行测试中有所变化,只需创建一个不同的类。

            【讨论】:

              【解决方案11】:

              同时运行可能会导致意想不到的结果。例如,我刚刚发现,虽然我的带有 200 个测试的测试套件在一个接一个执行时通过,但它在并发执行时失败,我发现这不是线程安全问题,而是依赖于另一个测试的测试,是一件坏事,我可以解决这个问题。

              Mycila work on JUnit ConcurrentJunitRunner and ConcurrentSuite 很有趣。与最新的 GA 版本相比,这篇文章似乎有点过时了,在我的示例中,我将展示更新后的用法。

              像下面这样注解一个测试类会导致并发执行测试方法,并发级别为6:

              import com.mycila.junit.concurrent.ConcurrentJunitRunner;
              import com.mycila.junit.concurrent.Concurrency;
              
              @RunWith(ConcurrentJunitRunner.class)
              @Concurrency(6)
              public final class ATest {
              ...
              

              你也可以同时运行所有的测试类:

              import com.mycila.junit.concurrent.ConcurrentSuiteRunner;
              
              @RunWith(ConcurrentSuiteRunner.class)
              @Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
              public class MySuite {
              }
              

              Maven dependency 是:

              <dependency>
                  <groupId>com.mycila</groupId>
                  <artifactId>mycila-junit</artifactId>
                  <version>1.4.ga</version>
              </dependency> 
              

              我目前正在研究如何多次运行方法并与此包同时运行。如果有人有一个例子让我知道,这可能已经是可能的了,在我自制的解决方案下面。

              @Test
              public final void runConcurrentMethod() throws InterruptedException {
                  ExecutorService exec = Executors.newFixedThreadPool(16);
                  for (int i = 0; i < 10000; i++) {
                      exec.execute(new Runnable() {
                           @Override
                           public void run() {
                               concurrentMethod();
                           }
                      });
                  }
                  exec.shutdown();
                  exec.awaitTermination(50, TimeUnit.SECONDS);
              }
              
              private void concurrentMethod() {
                  //do and assert something
              }
              

              正如其他人所指出的那样,您永远无法确定是否会出现并发错误,但在数万或数十万次执行中,并发数为 16 时,统计数据就在您身边。

              【讨论】:

              • 仅供参考,根据您的自制解决方案,您可以很容易地实现自定义 JUnit 规则,类似于 this article BTW 我现在正在尝试做同样的事情:)
              【解决方案12】:

              你也可以试试HavaRunner。它默认并行运行测试。

              【讨论】:

                猜你喜欢
                • 2011-04-24
                • 1970-01-01
                • 1970-01-01
                • 2021-01-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-01-12
                相关资源
                最近更新 更多