如何运行并发单元测试?

时间:2009-08-04 10:37:51

标签: unit-testing concurrency junit

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

假设我有一个班级

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

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

我想同时测试对这个postMessage的多次访问。 有什么建议吗?我希望针对我的所有setter函数(或任何涉及create / update / delete操作的方法)运行这种并发性测试。

12 个答案:

答案 0 :(得分:21)

不幸的是,我不相信你可以通过运行时测试明确地证明你的代码是线程安全的。你可以根据需要抛出任意数量的线程,它可能会/可能不会通过,具体取决于调度。

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

答案 1 :(得分:16)

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

  

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

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

答案 2 :(得分:9)

您只能证明并发错误的存在,而不是缺席

但是你可以编写一个专门的测试运行器来生成几个并发线程,然后调用你的@Test注释方法。

答案 3 :(得分:7)

在.NET中,有TypeMock RacerMicrosoft CHESS等工具专为单元测试并发而设计。这些工具不仅可以找到死锁等多线程错误,还可以为您提供重现错误的线程交错集。

我认为Java世界有类似的东西。

答案 4 :(得分:5)

并发运行可能会导致意外结果。例如,我刚刚发现,虽然我的测试套件有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,统计数据就在你身边。

答案 5 :(得分:2)

尝试查看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;
}

以上将在paralell中运行相同的JUnit测试类5次。如果你想在你的并列测试中有变化,那就创建一个不同的类。

答案 6 :(得分:1)

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

您需要在不同的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 :(得分:1)

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

答案 8 :(得分:1)

您可以使用tempus-fugit库并行运行测试方法多次以模拟负载测试类型环境。虽然之前的注释指出post方法是同步的并且受到保护,但是可能涉及其自身未受保护的关联成员或方法,因此其可能加载/浸泡类型测试可以抓住这些。我建议你设置一个相当粗粒度/端到端的测试,以便最好地捕获任何循环漏洞。

请参阅documentation的JUnit集成部分。

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

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

这里最好的方法是使用系统测试来查询代码,看看它是否会崩溃。然后使用单元测试来检查逻辑正确性。我接近这个的方法是为异步调用创建一个代理,让它在测试中同步进行。

但是,如果你想在完全安装和完整环境以外的级别执行此操作,可以在junit中通过在单独的线程中创建对象来执行此操作,然后创建大量线程来触发对对象的请求并阻止直到它完成的主线程。如果你做得不对,这种方法可以使测试间歇性地失败。

答案 11 :(得分:0)

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