我有一个大型的JUnit测试套件,出于两个原因,我想在其中同时运行所有测试:
- 利用多个内核来更快地运行整个测试套件
- 希望检测到由于非线程安全的全局对象而导致的一些错误
我知道这将迫使我重构一些代码以使其具有线程安全性,但是我认为这是一件好事:-)
使JUnit同时运行所有测试的最佳方法是什么?
Answers:
您是否固定使用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)
您甚至可以WildcardPatternSuite
通过直接从该套件进行扩展来利用功能的功能,而不是Suite
像前面的示例中那样。这样,您还可以按任意条件过滤单元测试fe-@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
在subdirecotries将执行的每个类发现,与端部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
在测试类中维护一个对fe的静态引用,该变量在before类方法执行一次之后设置。如果此布尔变量为true,则只会返回其他任何初始化方法调用。这样可以防止多个线程丢弃已初始化的数据
您也可以尝试HavaRunner。它是一个JUnit运行程序,默认情况下并行运行测试。
HavaRunner还具有便捷的套件:您可以通过将注释添加@PartOf(YourIntegrationTestSuite.class)
到类来声明测试是套件的成员。这种方法不同于JUnit的方法,在JUnit中,您在套件类中声明套件成员身份。
此外,HavaRunner套件可能会初始化重量级对象,例如嵌入式Web应用程序容器。然后,HavaRunner将这个重量级对象传递给每个套件成员的构造函数。这样就消除了对@BeforeClass
和@AfterClass
注释的需要,因为它们会促进全局可变状态,从而使并行化变得困难,因为它们存在问题。
最后,HavaRunner具有方案-一种针对不同数据运行相同测试的方法。场景减少了重复测试代码的需要。
HavaRunner已在两个中型Java项目中进行了测试。
附言 我是HavaRunner的作者,感谢您对此的反馈。