【发布时间】:2023-03-08 14:27:02
【问题描述】:
我有一个大型 JUnit 测试套件,我很想同时运行所有测试,原因有两个:
- 利用多个内核更快地运行整个测试套件
- 希望能检测到由于非线程安全全局对象引起的一些错误
我认识到这将迫使我重构一些代码以使其成为线程安全的,但我认为这是一件好事 :-)
让 JUnit 同时运行所有测试的最佳方法是什么?
【问题讨论】:
标签: java multithreading unit-testing concurrency junit
我有一个大型 JUnit 测试套件,我很想同时运行所有测试,原因有两个:
我认识到这将迫使我重构一些代码以使其成为线程安全的,但我认为这是一件好事 :-)
让 JUnit 同时运行所有测试的最佳方法是什么?
【问题讨论】:
标签: java multithreading unit-testing concurrency junit
你固定在 JUnit 上吗? TestNG 提供了良好的开箱即用多线程测试,并且它与 JUnit 测试兼容(您需要进行一些更改)。例如,您可以运行这样的测试:
@Test(threadPoolSize = 3, invocationCount = 9, timeOut = 10000)
public void doSomething() {
...
}
这意味着doSomething() 方法将被 3 个不同的线程调用 9 次。
我强烈推荐 TestNG。
【讨论】:
我一直在寻找这个问题的答案,根据这里的答案以及我在其他地方读到的内容,目前似乎没有一种简单的开箱即用方式来运行现有测试并行使用 JUnit。或者如果我没有找到它。所以我写了一个简单的 JUnit Runner 来完成它。请随意使用它;有关 MultiThreadedRunner 类的完整解释和源代码,请参阅http://falutin.net/2012/12/30/multithreaded-testing-with-junit/。有了这个类,你可以像这样注释你现有的测试类:
@RunWith(MultiThreadedRunner.class)
【讨论】:
以下代码应满足您的要求,该要求取自德文书籍 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)
您甚至可以利用 f.e. 的功能。 WildcardPatternSuite 通过直接从该套件扩展,而不是像之前的示例中的 Suite。这使您能够进一步过滤单元测试 f.e.通过任何@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 内核数量。
我不确定它是否可以变得更简单:)
【讨论】:
显然在他的帖子“使用 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 {
}
【讨论】:
AtomicBoolean isInitialized 变量,它是在执行一次前类方法之后设置的。如果此布尔变量为真,则任何其他对 init 方法的调用都将返回。这可以防止多个线程丢弃已经初始化的数据
你也可以试试HavaRunner。它是一个 JUnit 运行器,默认情况下并行运行测试。
HavaRunner 也有方便的套件:您可以通过在类上添加注解 @PartOf(YourIntegrationTestSuite.class) 来声明测试是套件的成员。这种方法与 JUnit 不同,在 JUnit 中,您在套件类中声明套件成员资格。
此外,HavaRunner 套件可以初始化重量级对象,例如嵌入式 Web 应用程序容器。 HavaRunner 然后将这个重量级对象传递给每个套件成员的构造函数。这消除了对有问题的 @BeforeClass 和 @AfterClass 注释的需求,因为它们促进了全局可变状态,这反过来又使并行化变得困难。
最后,HavaRunner 有场景——一种针对不同数据运行相同测试的方法。场景减少了重复测试代码的需要。
HavaRunner 已经在两个中型 Java 项目中进行了实战测试。
附言。我是 HavaRunner 的作者,非常感谢您对此的反馈。
【讨论】: