Junit执行器Runner探索之旅

导读

单元测试是每个程序员必备的技能,而Runner是每个单元测试类必有属性。本文通过解读Junit源码,介绍junit中每个执行器的使用方法,让读者在单元测试时,可以灵活的使用Runner执行器。

01 背景

在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!

在今年的敏捷团队建设中,京东物流通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此京东物流的Runner探索之旅开始了!

02 RunWith

理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。

RunWith的注释是当一个类用@RunWith注释或扩展一个用@RunWith注释的类时,JUnit将调用它引用的类来运行该类中的测试,而不是内置到JUnit中的运行器,就是测试类根据指定运行方式进行运行。

代码如下:

public @interface RunWith { Class<? extends Runner> value;}

其中:Runner 就是指定的运行方式。

03 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 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 == ) { description = Description.createTestDescription(getTestClass.getJavaClass, testName(method), method.getAnnotations); methodDescriptions.putIfAbsent(method, description); }
return description;}@Overrideprotected List getChildren { return computeTestMethods;}

runChild :

  • 调用describeChild

  • 判断方法是否包含@Ignore注解,有就触发TestIgnored事件通知

  • 构造Statement回调,通过methodBlock构造并装饰测试方法

  • 执行测试方法调用statement.evaluate

describeChild : 对测试方法创建Description并进行缓存

getChildren:返回运行测试的方法。 默认实现返回该类和超类上所有用@Test标注的未重写的方法

3.1.2BlockJUnit4ClassRunnerWithParameters

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 parameters; public TestWithParameters(String name, TestClass testClass, List 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(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)); }}

运行结果:

Junit执行器Runner探索之旅

图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"); }}

执行结果:

Junit执行器Runner探索之旅

图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 {}

运行结果:

Junit执行器Runner探索之旅

图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 {}

运行结果:

Junit执行器Runner探索之旅

图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"); } }}

运行结果:没有执行内部类的测试方法。

Junit执行器Runner探索之旅

图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"); } }}

执行结果:执行了内部类的测试方法。

Junit执行器Runner探索之旅

图7 Enclosed测试代码的执行结果

3.1.8 Parameterized

Parameterized实现参数化测试。 运行参数化的测试类时,会为测试方法和测试数据元素的交叉乘积创建实例。

Parameterized包含一个提供数据的方法,这个方法必须增加Parameters注解,并且这个方法必须是静态static的,并且返回一个集合Collection,Collection中的值长度必须相等。

测试代码:

@RunWith(Parameterized.class)public class ParameterizedTest { @Parameterized.Parameters public static Collection 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)); }}

运行结果:

Junit执行器Runner探索之旅

图8 Parameterized测试代码的执行结果

3.2 JUnit38ClassRunner

JUnit38ClassRunner及其子类是Junit4的内部运行器,有一个内部类OldTestClassAdaptingListener

实现了TestListener接口。

3.3 ErrorReportingRunner

ErrorReportingRunner也是Junit4运行错误时抛出的异常,代码如下:

private final List causes;
public ErrorReportingRunner(Class<?> testClass, Throwable cause) { if (testClass == ) { throw new PointerException("Test class cannot be "); } this.testClass = testClass; causes = getCauses(cause);}
private List 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) != ) { return new IgnoredClassRunner(testClass); } return ; }}

当测试时想忽略某些方法时,可以通过继承IgnoredClassRunner增加特定注解实现。

04 小结

Runner探索之旅结束了,可是单元测试之路才刚刚开始。不同的Runner组合,让单元测试更加灵活,测试场景更加丰富,更好的实现了测试驱动开发,让系统更加牢固可靠。

活动预告

Junit执行器Runner探索之旅

本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

高可用架构

改变互联网的构建方式

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章