并发 JUnit 测试

发布于 2024-10-16 18:09:58 字数 198 浏览 2 评论 0原文

我有一个大型 JUnit 测试套件,我很想同时运行所有测试,原因有两个:

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

我认识到这一点这将迫使我重构一些代码以使其线程安全,但我认为这是一件好事:-)

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

I have a large JUnit test suite, where I'd quite like to run all the tests concurrently for two reasons:

  • Exploit multiple cores to run the whole test suite faster
  • Hopefully detect some errors due to non-thread-safe global objects

I recognise that this will force me to refactor some code to make it thread-safe, but I consider that to be a good thing :-)

What's the best way to get JUnit to run all the tests concurrently?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

许仙没带伞 2024-10-23 18:09:58

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

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

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

我强烈推荐TestNG

Are you fixed to JUnit? TestNG provides good multi thread testing out of the box and it's compatible with JUnit tests (you need to make a few changes). For example you could run a test like this:

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

This would mean that the doSomething() method will be invoked 9 times by 3 different threads.

I highly recommend TestNG.

嗫嚅 2024-10-23 18:09:58

我一直在寻找这个问题的答案,根据这里的答案以及我在其他地方读到的内容,目前似乎没有一种简单的开箱即用的方法来使用并行运行现有测试JUnit。或者如果有的话我没找到。所以我写了一个简单的 JUnit Runner 来完成这个任务。请放心使用;请参阅http://falutin.net/2012/12/30/multithreaded-testing-with- junit/ 以获得完整的解释和 MultiThreadedRunner 类的源代码。使用此类,您可以像这样注释现有的测试类:

@RunWith(MultiThreadedRunner.class)

I was looking for an answer to exactly this question, and based on the answers here, and what I read elsewhere, it appears as if there isn't currently an easy out-of-the-box way to run existing tests in parallel using JUnit. Or if there is I didn't find it. So I wrote a simple JUnit Runner that accomplishes that. Please feel free to use it; see http://falutin.net/2012/12/30/multithreaded-testing-with-junit/ for a complete explanation and the source code of the MultiThreadedRunner class. With this class, you can just annotate your existing test class(es) like this:

@RunWith(MultiThreadedRunner.class)
深巷少女 2024-10-23 18:09:58

以下代码应满足您的要求,该代码取自德语书籍 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)

您甚至可以通过直接从该套件(而不是前面示例中的 Suite)扩展来利用 WildcardPatternSuite 的功能。这使您能够进一步通过任何 @Category 过滤单元测试 - 一个仅并行执行 UnitTest 带注释的类别的 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 将执行在以 Test 结尾的子目录中找到的每个类,并且在以下位置指定了 @Category(UnitTest.class)并行 - 取决于可用 CPU 核心的数量。

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

The following code should achieve your requirements which was taken from the German book JUnit Profiwissen which contains some hints to either tests stuff in parallel or on how to reduce execution time through using multiple cores instead of only a single core.

JUnit 4.6 introduced a ParallelComputer class which offered the parallel execution of tests. However, this functionality was not publicly accessible until JUnit 4.7 which provided the possibility to set a custom scheduler for the parent runner.

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());
    }
}

If you now annotate a test-class with @RunWith(ParallelRunner.class) each method will run within its own thread. Furthermore, there will be as many active threads (only) as CPU-cores are available at the executing machine.

If multiple classes should be executed in parallel you can define a customized suite like this:

public class ParallelSuite extends Suite {

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

and then change @RunWith(Suite.class) with @RunWith(ParallelSuite.class)

You can even leverage the functionality of f.e. the WildcardPatternSuite by extending directly from that suite instead of Suite as in the example before. This enables you furthermore to filter unit-tests f.e. by any @Category - a TestSuite which only executes UnitTest annotated categories in parallel could look like this:

public interface UnitTest {

}

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

}

A simple test case could now look like this:

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

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

The UnitTestSuite will execute each class found in subdirecotries that ends with Test and has a @Category(UnitTest.class) specified in parallel - depending on the number of CPU cores available.

I'm not sure if it can get simpler than that :)

仅冇旳回忆 2024-10-23 18:09:58

显然,Mathieu Carbou 在他的文章“Concurrent JUnit Tests With RunnerScheduler”中已经完成了并发的实现,这可能会有所帮助!

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 {
}

Apparently in his post "Concurrent JUnit Tests With RunnerScheduler" Mathieu Carbou has done implementation for concurrence that could helps!

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);
    }
}

Multiple JUnits:

@RunWith(ConcurrentSuite.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}
面犯桃花 2024-10-23 18:09:58

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

HavaRunner 还有方便的套件:您可以通过将注释 @PartOf(YourIntegrationTestSuite.class) 添加到类上来将测试声明为套件的成员。此方法与 JUnit 不同,在 JUnit 中,您在套件类中声明套件成员资格。

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

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

HavaRunner 已经在两个中型 Java 项目中经过了实际测试。

诗。我是 HavaRunner 的作者,非常感谢您的反馈。

You can also try HavaRunner. It is a JUnit runner that runs tests in parallel by default.

HavaRunner also has handy suites: you can declare a test to be a member of a suite by adding the annotation @PartOf(YourIntegrationTestSuite.class) onto the class. This approach differs from JUnit's, where you declare the suite memberships in the suite class.

In addition, HavaRunner suites may intialise heavy-weight objects such as an embedded web application container. HavaRunner then passes this heavy-weight object to the constructor of each suite member. This removes the need for the @BeforeClass and @AfterClass annotations, which are problematic, because they promote global mutable state, which in turn makes parallelisation difficult.

Lastly, HavaRunner has scenarios – a way to run the same test against different data. Scenarios reduce the need to duplicate test code.

HavaRunner has been battle-tested in two mid-size Java projects.

Ps. I'm the author of HavaRunner, and I'd appreciate your feedback on it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文