单元测试是每个程序员必备的技能,而Runner是每个单元测试类必有属性。本文通过解读Junit源码,介绍junit中每个执行器的应用办法,让读者在单元测试时,能够灵便的应用Runner执行器。
一、背景
在往年的麻利团队建设中,京东物流通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此京东物流的Runner探索之旅开始了!
二、RunWith
RunWith的正文是当一个类用@RunWith正文或扩大一个用@RunWith正文的类时,JUnit将调用它援用的类来运行该类中的测试,而不是内置到JUnit中的运行器,就是测试类依据指定运行形式进行运行。
代码如下:
public @interface RunWith { Class<? extends Runner> value();}
其中:Runner 就是指定的运行形式。
三、Runner
Runner的作用是通知Junit如何运行一个测试类,它是一个抽象类。通过RunWith 指定具体的实现类,如果不指定默认应用BlockJUnit4ClassRunner,Runner的代码如下:
public abstract class Runner implements Describable { public abstract Description getDescription(); public abstract void run(RunNotifier notifier); public int testCount() { return getDescription().testCount(); }}
3.1 ParentRunner
ParentRunner是一个抽象类,提供了大多数特定于运行器的性能,是常常应用运行器的父节点。实现了Filterable,Sortable接口,能够过滤和排序子对象。
提供了3个形象办法:
protected abstract List<T> getChildren();protected abstract Description describeChild(T child);protected abstract void runChild(T child, RunNotifier notifier);
3.1.1 BlockJUnit4ClassRunner
BlockJUnit4ClassRunner是Juint4默认的运行器,具备与旧的测试类运行器(JUnit4ClassRunner)完全相同的行为。
ParentRunner3个形象办法的实现如下:
@Overrideprotected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { runLeaf(methodBlock(method), description, notifier); }}@Overrideprotected Description describeChild(FrameworkMethod method) { Description description = methodDescriptions.get(method); if (description == null) { description = Description.createTestDescription(getTestClass().getJavaClass(), testName(method), method.getAnnotations()); methodDescriptions.putIfAbsent(method, description); } return description;}@Overrideprotected List<FrameworkMethod> getChildren() { return computeTestMethods();}
runChild() :
- 调用describeChild()
- 判断办法是否蕴含@Ignore注解,有就触发TestIgnored事件告诉
- 结构Statement回调,通过methodBlock()结构并装璜测试方法
- 执行测试方法调用statement.evaluate()
describeChild() : 对测试方法创立Description并进行缓存
getChildren():返回运行测试的办法。 默认实现返回该类和超类上所有用@Test标注的未重写的办法
3.1.2 BlockJUnit4ClassRunnerWithParameters
BlockJUnit4ClassRunnerWithParameters是一个反对参数的BlockJUnit4ClassRunner。参数能够通过构造函数注入或注入到带正文的字段中。参数蕴含名称、测试类和一组参数。
private final Object[] parameters;private final String name;public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test) throws InitializationError { super(test.getTestClass().getJavaClass()); parameters = test.getParameters().toArray( new Object[test.getParameters().size()]); name = test.getName();}
参数代码如下:
public class TestWithParameters { private final String name; private final TestClass testClass; private final List<Object> parameters; public TestWithParameters(String name, TestClass testClass, List<Object> parameters) { notNull(name, "The name is missing."); notNull(testClass, "The test class is missing."); notNull(parameters, "The parameters are missing."); this.name = name; this.testClass = testClass; this.parameters = unmodifiableList(new ArrayList<Object>(parameters)); }
BlockJUnit4ClassRunnerWithParameters个别联合Parameterized应用
3.1.3 Theories
Theories容许对有限数据点集的子集测试某种性能。提供一组参数的排列组合值作为待测办法的输出参数。同时留神到在应用Theories这个Runner的时候,待测办法能够领有输出参数,能够使您的测试更加灵便。
测试代码如下:
@RunWith(Theories.class)public class TheoriesTest { @DataPoints public static String[] tables = {"方桌子", "圆桌子"}; @DataPoints public static int[] counts = {4,6,8}; @Theory public void testMethod(String table, int count){ System.out.println(String.format("一套桌椅有一个%s和%d个椅子", table, count)); }}
运行后果:
图2 Theories测试代码的执行后果
3.1.4 JUnit4
JUnit4是Junit4默认执行器的别名,想要显式地将一个类标记为JUnit4类,应该应用@RunWith(JUnit4.class),而不是,应用@RunWith(BlockJUnit4ClassRunner.class)
3.1.5 Suite
Suite容许您手动构建蕴含来自许多类的测试的套件.通过Suite.SuiteClasses定义要执行的测试类,一键执行所有的测试类。
测试代码如下:
@RunWith(Suite.class)@Suite.SuiteClasses({Suite_test_a.class,Suite_test_b.class,Suite_test_c.class })public class Suite_main {}public class Suite_test_a { @Test public void testRun(){ System.out.println("Suite_test_a_running"); }}public class Suite_test_b { @Test public void testRun(){ System.out.println("Suite_test_b_running"); }}public class Suite_test_c { @Test public void testRun(){ System.out.println("Suite_test_c_running"); }}
执行后果:
图3 Suite测试代码的执行后果
如后果所示:执行MainSuit时顺次执行了Suite\_test\_a,Suite\_test\_b,Suite\_test\_c 的办法,实现了一键执行。
3.1.6 Categories
Categories在给定的一组测试类中,只运行用带有@ inclecategory标注的类别或该类别的子类型标注的类和办法。通过ExcludeCategory过滤类型。
测试代码如下:
public interface BlackCategory {}public interface WhiteCategory {}public class Categories_test_a { @Test @Category(BlackCategory.class) public void testFirst(){ System.out.println("Categories_test_a_testFirst_running"); } @Test @Category(WhiteCategory.class) public void testSecond(){ System.out.println("Categories_test_a_testSecond_running"); }}public class Categories_test_b { @Test @Category(WhiteCategory.class) public void testFirst(){ System.out.println("Categories_test_b_testFirst_running"); } @Test @Category(BlackCategory.class) public void testSecond(){ System.out.println("Categories_test_b_testSecond_running"); }}
执行带WhiteCategory的办法
@RunWith(Categories.class)@Categories.IncludeCategory(WhiteCategory.class)@Categories.ExcludeCategory(BlackCategory.class)@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class })public class Categories_main {}
运行后果:
图4 Categories测试代码WhiteCategory分组执行后果
执行带BlackCategory的办法
@RunWith(Categories.class)@Categories.IncludeCategory(BlackCategory.class)@Categories.ExcludeCategory(WhiteCategory.class)@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class })public class Categories_main {}
运行后果:
图5 Categories测试代码BlackCategory分组执行后果
如运行后果所示,通过IncludeCategory,ExcludeCategory能够灵便的运行具体的测试类和办法。
3.1.7 Enclosed
Enclosed应用Enclosed运行外部类,外部类中的测试将被运行。 您能够将测试放在外部类中,以便对它们进行分组或共享常量。
测试代码:
public class EnclosedTest { @Test public void runOutMethou(){ System.out.println("EnclosedTest_runOutMethou_running"); } public static class EnclosedInnerTest { @Test public void runInMethou(){ System.out.println("EnclosedInnerTest_runInMethou_running"); } }}
运行后果:没有执行外部类的测试方法。
图6 Enclosed测试代码的执行后果
应用Enclosed执行器:
@RunWith(Enclosed.class)public class EnclosedTest { @Test public void runOutMethou(){ System.out.println("EnclosedTest_runOutMethou_running"); } public static class EnclosedInnerTest { @Test public void runInMethou(){ System.out.println("EnclosedInnerTest_runInMethou_running"); } }}
执行后果:执行了外部类的测试方法。
图7 Enclosed测试代码的执行后果
3.1.8 Parameterized
Parameterized实现参数化测试。 运行参数化的测试类时,会为测试方法和测试数据元素的穿插乘积创立实例。
Parameterized蕴含一个提供数据的办法,这个办法必须减少Parameters注解,并且这个办法必
须是动态static的,并且返回一个汇合Collection,Collection中的值长度必须相等。
测试代码:
@RunWith(Parameterized.class)public class ParameterizedTest { @Parameterized.Parameters public static Collection<Object[]> initData(){ return Arrays.asList(new Object[][]{ {"小白",1,"鸡腿"},{"小黑",2,"面包"},{"小红",1,"苹果"} }); } private String name; private int count; private String food; public ParameterizedTest(String name, int count, String food) { this.name = name; this.count = count; this.food = food; } @Test public void eated(){ System.out.println(String.format("%s中午吃了%d个%s",name,count,food)); }}
运行后果:
图8 Parameterized测试代码的执行后果
3.2 JUnit38ClassRunner
JUnit38ClassRunner及其子类是Junit4的外部运行器,有一个外部类OldTestClassAdaptingListener
实现了TestListener接口。
3.3 ErrorReportingRunner
ErrorReportingRunner也是Junit4运行谬误时抛出的异样,代码如下:
private final List<Throwable> causes;public ErrorReportingRunner(Class<?> testClass, Throwable cause) { if (testClass == null) { throw new NullPointerException("Test class cannot be null"); } this.testClass = testClass; causes = getCauses(cause);} private List<Throwable> getCauses(Throwable cause) { if (cause instanceof InvocationTargetException) { return getCauses(cause.getCause()); } if (cause instanceof InitializationError) { return ((InitializationError) cause).getCauses(); } if (cause instanceof org.junit.internal.runners.InitializationError) { return ((org.junit.internal.runners.InitializationError) cause) .getCauses(); } return Arrays.asList(cause); }
当junit运行谬误时,会抛出ErrorReportingRunner,例如:
public Runner getRunner() { try { Runner runner = request.getRunner(); fFilter.apply(runner); return runner; } catch (NoTestsRemainException e) { return new ErrorReportingRunner(Filter.class, new Exception(String .format("No tests found matching %s from %s", fFilter .describe(), request.toString()))); }}
3.4 IgnoredClassRunner
IgnoredClassRunner是当测试的办法蕴含Ignore注解时,会疏忽该办法。
public class IgnoredClassRunner extends Runner { private final Class<?> clazz; public IgnoredClassRunner(Class<?> testClass) { clazz = testClass; } @Override public void run(RunNotifier notifier) { notifier.fireTestIgnored(getDescription()); } @Override public Description getDescription() { return Description.createSuiteDescription(clazz); }}
IgnoredClassRunner的应用
public class IgnoredBuilder extends RunnerBuilder { @Override public Runner runnerForClass(Class<?> testClass) { if (testClass.getAnnotation(Ignore.class) != null) { return new IgnoredClassRunner(testClass); } return null; }}
当测试时想疏忽某些办法时,能够通过继承IgnoredClassRunner减少特定注解实现。
四、小结
Runner探索之旅完结了,可是单元测试之路才刚刚开始。不同的Runner组合,让单元测试更加灵便,测试场景更加丰盛,更好的实现了测试驱动开发,让零碎更加牢固牢靠。
作者:京东物流 陈昌浩
起源:京东云开发者社区