并发JUnit测试


72

我有一个大型的JUnit测试套件,出于两个原因,我想在其中同时运行所有测试:

  • 利用多个内核来更快地运行整个测试套件
  • 希望检测到由于非线程安全的全局对象而导致的一些错误

我知道这将迫使我重构一些代码以使其具有线程安全性,但是我认为这是一件好事:-)

使JUnit同时运行所有测试的最佳方法是什么?

Answers:


31

您是否固定使用JUnit?TestNG提供了开箱即用的良好多线程测试,并且与JUnit测试兼容(您需要进行一些更改)。例如,您可以运行以下测试:

@Test(threadPoolSize = 3, invocationCount = 9,  timeOut = 10000)
public void doSomething() {
...
}

这意味着该doSomething()方法将被3个不同的线程调用9次。

我强烈推荐TestNG


+1为TestNG,我将其用于所有线程安全测试。它还具有不错的参数化测试。

25
由于未实际回答OP问题而被否决。他特别指出,他想跨线程运行他的整个套件并多次运行一个方法
tddmonkey 2012年

7
@TobyHobson这是测试线程安全性的糟糕方法。而且根据定义,不是单元测试,因为它不是确定性的。而且TestNG包含许多错误。我最近修复了其中的一些,但它们没有被撤消(没有建议),TestNG处于非活动状态。而是尝试使用Thread Weaver(code.google.com/p/thread-weaver
拉斐尔·温特

22

我一直在寻找一个恰好是这个问题的答案,并且根据这里的答案以及我在其他地方所读到的内容,似乎似乎没有一种简便的现成方法可以使用JUnit的。或者,如果没有找到。因此,我编写了一个简单的JUnit Runner来完成该任务。请随时使用它;有关完整的说明和MultiThreadedRunner类的源代码,请参见http://falutin.net/2012/12/30/multithreaded-testing-with-junit/。使用此类,您可以像这样注释现有的测试类:

@RunWith(MultiThreadedRunner.class)

您对如何实现并发套件运行器,而不仅是并发测试运行器有一个想法吗?可能也有帮助:P
斯特凡皮埃特

我没看过,斯特凡。但是让测试运行程序非常容易,我敢打赌套件运行程序也不会很难。如果您解决了问题,我很乐意将您的代码添加到我的帖子中:)
Mike Sokolov

链接已死。
Brain

它又活了!
Mike Sokolov

22

以下代码应能满足您的要求,该要求取自德语书籍JUnit Profiwissen,其中包含一些提示以并行测试内容或如何通过使用多个内核而不是仅一个内核来减少执行时间。

JUnit 4.6引入了ParallelComputer类,该类提供了并行执行测试的功能。但是,直到JUnit 4.7才可以公开访问此功能,从而可以为父运行器设置自定义调度程序。

public class ParallelScheduler implements RunnerScheduler {

    private ExecutorService threadPool = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors());

    @Override
    public void schedule(Runnable childStatement) {
        threadPool.submit(childStatement);
    }

    @Override
    public void finished() {
        try {
            threadPool.shutdown();
            threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Got interrupted", e);
        }
    }
}

public class ParallelRunner extends BlockJUnit4ClassRunner {

    public ParallelRunner(Class<?> klass) throws InitializationError {
        super(klass);
        setScheduler(new ParallelScheduler());
    }
}

如果现在用@RunWith(ParallelRunner.class)每个方法注释一个测试类,则每个方法将在其自己的线程中运行。此外,执行机器上的活动线程(仅)与CPU内核可用数量一样多。

如果应并行执行多个类,则可以定义一个自定义套件,如下所示:

public class ParallelSuite extends Suite {

    public ParallelSuite(Class<?> klass, RunnerBuilder builder) 
      throws InitializationError {
        super(klass, builder);
        setScheduler(new ParallelScheduler());
    }
}

然后@RunWith(Suite.class)@RunWith(ParallelSuite.class)

您甚至可以WildcardPatternSuite通过直接从该套件进行扩展来利用功能的功能,而不是Suite像前面的示例中那样。这样,您还可以按任意条件过滤单元测试fe-@CategoryUnitTest并行执行带注释的类别的TestSuite可能如下所示:

public interface UnitTest {

}

@RunWith(ParallelSuite.class)
@SuiteClasses("**/*Test.class")
@IncludeCategories(UnitTest.class)
public class UnitTestSuite {

}

现在,一个简单的测试用例可能如下所示:

@Category(UnitTest.class)
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {

    @Test
    public void testSomething() {
        ...
    }
}

UnitTestSuite在subdirecotries将执行的每个类发现,与端部Test并具有@Category(UnitTest.class)在平行指定-这取决于可用的CPU内核的数量。

我不确定它是否可以比这更简单:)


6
您不必重写此代码。作者发布了一个名为JUnit Toolbox的库:code.google.com/p/junit-toolbox
Stefan Birkner,2015年

6

显然,在他的文章“使用RunnerScheduler进行并发JUnit测试”中, Mathieu Carbou为实现并发做了一些实现,这可能会有所帮助!

http://dzone.com/articles/concurrent-junit-tests

OneJunit

@RunWith(ConcurrentJunitRunner.class)
@Concurrent(threads = 6)
public final class ATest {
 
    @Test public void test0() throws Throwable { printAndWait(); }
    @Test public void test1() throws Throwable { printAndWait(); }
    @Test public void test2() throws Throwable { printAndWait(); }
    @Test public void test3() throws Throwable { printAndWait(); }
    @Test public void test4() throws Throwable { printAndWait(); }
    @Test public void test5() throws Throwable { printAndWait(); }
    @Test public void test6() throws Throwable { printAndWait(); }
    @Test public void test7() throws Throwable { printAndWait(); }
    @Test public void test8() throws Throwable { printAndWait(); }
    @Test public void test9() throws Throwable { printAndWait(); }

    void printAndWait() throws Throwable {
        int w = new Random().nextInt(1000);
        System.out.println(String.format("[%s] %s %s %s",Thread.currentThread().getName(), getClass().getName(), new Throwable       ().getStackTrace()[1].getMethodName(), w));
        Thread.sleep(w);
    }
}

多个JUnit:

@RunWith(ConcurrentSuite.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}

1
这很好,但似乎不适用于包含@ BeforeClass和@ Before方法的测试。它尝试为每个线程运行@ BeforeClass方法,而不是仅运行一次。对于我们的设置,这意味着5个线程同时删除和重建数据库:(
Splaktar 2015年

@Splaktar解决此问题的一种方法是AtomicBoolean isInitialized在测试类中维护一个对fe的静态引用,该变量在before类方法执行一次之后设置。如果此布尔变量为true,则只会返回其他任何初始化方法调用。这样可以防止多个线程丢弃已初始化的数据
Roman Vottner

2

您也可以尝试HavaRunner。它是一个JUnit运行程序,默认情况下并行运行测试。

HavaRunner还具有便捷的套件:您可以通过将注释添加@PartOf(YourIntegrationTestSuite.class)到类来声明测试是套件的成员。这种方法不同于JUnit的方法,在JUnit中,您在套件类中声明套件成员身份。

此外,HavaRunner套件可能会初始化重量级对象,例如嵌入式Web应用程序容器。然后,HavaRunner将这个重量级对象传递给每个套件成员的构造函数。这样就消除了对@BeforeClass@AfterClass注释的需要,因为它们会促进全局可变状态,从而使并行化变得困难,因为它们存在问题。

最后,HavaRunner具有方案-一种针对不同数据运行相同测试的方法。场景减少了重复测试代码的需要。

HavaRunner已在两个中型Java项目中进行了测试。

附言 我是HavaRunner的作者,感谢您对此的反馈。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.