并发执行测试用例目的很简单:就是为了高效使用资源降低测试时间,俗话说天下武功,唯快不破,对,就是求追速度。
Junit中有关并行执行测试的关键组件为ParallelComputer,ParallelComputer的基类为Computer。Computer在Junit项目中是一个执行runners或者suites的角色(Runner是一个执行测试用例的执行器;Suite继承至Runner,Suite允许将多个测试用例封装到一个suite中)。所以为了并发测试就需要使用ParallelComputer类,先简单分析一下ParallelComputer的源码简单了解一下实现原理,对以后的使用应该帮助还是比较有好处的。ParallelComputer源码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.runner.Computer;
import org.junit.runner.Runner;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.RunnerScheduler;
public class ParallelComputer extends Computer {
private final boolean classes;
private final boolean methods;
//参数1:测试类是否并发执行, 参数2:测试方法是否并发执行
public ParallelComputer(boolean classes, boolean methods) {
this.classes = classes;
this.methods = methods;
}
public static Computer classes() {
return new ParallelComputer(true, false);
}
public static Computer methods() {
return new ParallelComputer(false, true);
}
// 实现并发执行的关键方法,在该方法中使用线程池实现并发测试
private static Runner parallelize(Runner runner) {
if (runner instanceof ParentRunner) {
// 重点代码处:setScheduler方法是设置测试执行器(Runner)为并行执行器,因此可以得以并行执行
((ParentRunner<?>) runner).setScheduler(new RunnerScheduler() {
//创建一个线程池,用于执行测试用例
private final ExecutorService fService = Executors.newCachedThreadPool();
//用于提交测试用例
public void schedule(Runnable childStatement) {
fService.submit(childStatement);
}
//所有测试用例执行完毕之后释放资源
public void finished() {
try {
fService.shutdown();
fService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
});
}
return runner;
}
@Override
public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes)
throws InitializationError {
Runner suite = super.getSuite(builder, classes);
//通过classes字段是否并行执行测试用例类
return this.classes ? parallelize(suite) : suite;
}
@Override
protected Runner getRunner(RunnerBuilder builder, Class<?> testClass)
throws Throwable {
Runner runner = super.getRunner(builder, testClass);
//通过methods方法判断是否并行执行测试方法
return methods ? parallelize(runner) : runner;
}
}
ParallelComputer的关键代码说明见注释。接下来看一下如何使用并发执行测试用例,测试用例如下:
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
/**
* Created by daxin on 2018/6/9.
*/
public class TestA {
@Test
public void testA() {
ConcurrentTest.printThreadName();
assertThat(3, is(1));
}
@Test
public void testB() {
ConcurrentTest.printThreadName();
assertThat(3, not(1));
}
}
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
public class TestB {
@Test
public void testC() {
ConcurrentTest.printThreadName();
assertThat(3, greaterThan(1));
}
@Test
public void testD() {
ConcurrentTest.printThreadName();
assertThat(3, lessThan(1));
}
}
并发测试的示例代码:
import org.junit.experimental.ParallelComputer;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
public class ConcurrentTest {
/**
* 该方法在真是测试环境下没有实际用途,只是为了验证是否是并发测试
*/
public static void printThreadName() {
System.out.println("线程名字 = " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Class[] cls = {TestA.class, TestB.class};
//JUnitCore是一个执行测试用例的门面(facade设计模式)
//ParallelComputer:两个参数分别表示类和方法都并行执行
Result rt = JUnitCore.runClasses(new ParallelComputer(true, true), cls);
//打印执行结果
System.out.println(rt.getRunCount() + " " + rt.getRunTime());
System.out.println("================华丽的分割线==================");
rt.getFailures().forEach(System.out::println);
}
}
输出结果信息如下:
通过打印线程名字客户以看出JUint是以并发的方式执行测试用例。分割线下面可以看出各个存在问题的测试用例的信息。