导读
单元测试是每个程序员必备的技能,而Runner是每个单元测试类必有属性。本文通过解读Junit源码,介绍junit中每个执行器的使用方法,让读者在单元测试时,可以灵活的使用Runner执行器。
导读
单元测试是每个程序员必备的技能,而Runner是每个单元测试类必有属性。本文通过解读Junit源码,介绍junit中每个执行器的使用方法,让读者在单元测试时,可以灵活的使用Runner执行器。
RunWith的注释是当一个类用@RunWith注释或扩展一个用@RunWith注释的类时,JUnit将调用它引用的类来运行该类中的测试,而不是内置到JUnit中的运行器,就是测试类根据指定运行方式进行运行。
代码如下:
public @interface RunWith {Class<? extends Runner> value();}
其中:Runner 就是指定的运行方式。
03 Runner
public abstract class Runner implements Describable {public abstract Description getDescription();public abstract void run(RunNotifier notifier);public int testCount() {return getDescription().testCount();}}
ParentRunner是一个抽象类,提供了大多数特定于运行器的功能,是经常使用运行器的父节点。实现了Filterable,Sortable接口,可以过滤和排序子对象。
提供了3个抽象方法:
protected abstract List<T> getChildren();protected abstract Description describeChild(T child);protected abstract void runChild(T child, RunNotifier notifier);
BlockJUnit4ClassRunner是Juint4默认的运行器,具有与旧的测试类运行器(JUnit4ClassRunner)完全相同的行为。
ParentRunner3个抽象方法的实现如下:
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {Description description = describeChild(method);if (isIgnored(method)) {notifier.fireTestIgnored(description);} else {runLeaf(methodBlock(method), description, notifier);}}protected Description describeChild(FrameworkMethod method) {Description description = methodDescriptions.get(method);if (description == ) {description = Description.createTestDescription(getTestClass().getJavaClass(),testName(method), method.getAnnotations());methodDescriptions.putIfAbsent(method, description);}return description;}protected List<FrameworkMethod> getChildren() {return computeTestMethods();}
runChild() :
调用describeChild()
判断方法是否包含@Ignore注解,有就触发TestIgnored事件通知
构造Statement回调,通过methodBlock()构造并装饰测试方法
执行测试方法调用statement.evaluate()
describeChild() : 对测试方法创建Description并进行缓存
getChildren():返回运行测试的方法。 默认实现返回该类和超类上所有用@Test标注的未重写的方法
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) {not(name, "The name is missing.");not(testClass, "The test class is missing.");not(parameters, "The parameters are missing.");this.name = name;this.testClass = testClass;this.parameters = unmodifiableList(new ArrayList<Object>(parameters));}
Theories允许对无限数据点集的子集测试某种功能。提供一组参数的排列组合值作为待测方法的输入参数。同时注意到在使用Theories这个Runner的时候,待测方法可以拥有输入参数,可以使您的测试更加灵活。
测试代码如下:
@RunWith(Theories.class)public class TheoriesTest {@DataPointspublic static String[] tables = {"方桌子", "圆桌子"};@DataPointspublic static int[] counts = {4,6,8};@Theorypublic void testMethod(String table, int count){System.out.println(String.format("一套桌椅有一个%s和%d个椅子", table, count));}}
运行结果:
图2 Theories测试代码的执行结果
3.1.5 Suite
Suite允许您手动构建包含来自许多类的测试的套件.通过Suite.SuiteClasses定义要执行的测试类,一键执行所有的测试类。
测试代码如下:
.SuiteClasses({Suite_test_a.class,Suite_test_b.class,Suite_test_c.class })public class Suite_main {}public class Suite_test_a {public void testRun(){System.out.println("Suite_test_a_running");}}public class Suite_test_b {public void testRun(){System.out.println("Suite_test_b_running");}}public class Suite_test_c {public void testRun(){System.out.println("Suite_test_c_running");}}
执行结果:
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 {}
@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 {}
3.1.7 Enclosed
Enclosed使用Enclosed运行外部类,内部类中的测试将被运行。 您可以将测试放在内部类中,以便对它们进行分组或共享常量。
测试代码:
public class EnclosedTest {@Testpublic void runOutMethou(){System.out.println("EnclosedTest_runOutMethou_running");}public static class EnclosedInnerTest {@Testpublic void runInMethou(){System.out.println("EnclosedInnerTest_runInMethou_running");}}}
@RunWith(Enclosed.class)public class EnclosedTest {@Testpublic void runOutMethou(){System.out.println("EnclosedTest_runOutMethou_running");}public static class EnclosedInnerTest {@Testpublic void runInMethou(){System.out.println("EnclosedInnerTest_runInMethou_running");}}}
3.1.8 Parameterized
Parameterized实现参数化测试。 运行参数化的测试类时,会为测试方法和测试数据元素的交叉乘积创建实例。
Parameterized包含一个提供数据的方法,这个方法必须增加Parameters注解,并且这个方法必
须是静态static的,并且返回一个集合Collection,Collection中的值长度必须相等。
测试代码:
@RunWith(Parameterized.class)public class ParameterizedTest {@Parameterized.Parameterspublic 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;}@Testpublic void eated(){System.out.println(String.format("%s中午吃了%d个%s",name,count,food));}}
运行结果:
JUnit38ClassRunner及其子类是Junit4的内部运行器,有一个内部类OldTestClassAdaptingListener
实现了TestListener接口。
ErrorReportingRunner也是Junit4运行错误时抛出的异常,代码如下:
private final List<Throwable> causes;public ErrorReportingRunner(Class<?> testClass, Throwable cause) {if (testClass == ) {throw new PointerException("Test class cannot be ");}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);}
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;}public void run(RunNotifier notifier) {notifier.fireTestIgnored(getDescription());}public Description getDescription() {return Description.createSuiteDescription(clazz);}}
IgnoredClassRunner的使用
public class IgnoredBuilder extends RunnerBuilder {public Runner runnerForClass(Class<?> testClass) {if (testClass.getAnnotation(Ignore.class) != ) {return new IgnoredClassRunner(testClass);}return ;}}
当测试时想忽略某些方法时,可以通过继承IgnoredClassRunner增加特定注解实现。
04 小结
活动预告
本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

本文暂时没有评论,来添加一个吧(●'◡'●)