Junit执行器Runner探索之旅 | 京东云技术团队

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

一、背景

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

二、RunWith

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

代码如下:

1public @interface RunWith { 2 Class<? extends Runner> value(); 3} 4 5

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

三、Runner

Runner的作用是告诉Junit如何运行一个测试类,它是一个抽象类。通过RunWith 指定具体的实现类,如果不指定默认使用BlockJUnit4ClassRunner,Runner的代码如下:

1public abstract class Runner implements Describable { 2 public abstract Description getDescription(); 3 public abstract void run(RunNotifier notifier); 4 public int testCount() { 5 return getDescription().testCount(); 6 } 7} 8 9

3.1 ParentRunner

ParentRunner是一个抽象类,提供了大多数特定于运行器的功能,是经常使用运行器的父节点。实现了Filterable,Sortable接口,可以过滤和排序子对象。

提供了3个抽象方法:

1protected abstract List<T> getChildren(); 2protected abstract Description describeChild(T child); 3protected abstract void runChild(T child, RunNotifier notifier); 4 5

3.1.1 BlockJUnit4ClassRunner

BlockJUnit4ClassRunner是Juint4默认的运行器,具有与旧的测试类运行器(JUnit4ClassRunner)完全相同的行为。

ParentRunner3个抽象方法的实现如下:

1@Override 2protected void runChild(final FrameworkMethod method, RunNotifier notifier) { 3 Description description = describeChild(method); 4 if (isIgnored(method)) { 5 notifier.fireTestIgnored(description); 6 } else { 7 runLeaf(methodBlock(method), description, notifier); 8 } 9} 10@Override 11protected Description describeChild(FrameworkMethod method) { 12 Description description = methodDescriptions.get(method); 13 14 15 if (description == null) { 16 description = Description.createTestDescription(getTestClass().getJavaClass(), 17 testName(method), method.getAnnotations()); 18 methodDescriptions.putIfAbsent(method, description); 19 } 20 21 22 return description; 23} 24@Override 25protected List<FrameworkMethod> getChildren() { 26 return computeTestMethods(); 27} 28 29

runChild() :

  • 调用describeChild()

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

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

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

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

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

3.1.2 BlockJUnit4ClassRunnerWithParameters

BlockJUnit4ClassRunnerWithParameters是一个支持参数的BlockJUnit4ClassRunner。参数可以通过构造函数注入或注入到带注释的字段中。参数包含名称、测试类和一组参数。

1private final Object[] parameters; 2private final String name; 3public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test) 4 throws InitializationError { 5 super(test.getTestClass().getJavaClass()); 6 parameters = test.getParameters().toArray( 7 new Object[test.getParameters().size()]); 8 name = test.getName(); 9} 10 11

参数代码如下:

1public class TestWithParameters { 2 private final String name; 3 private final TestClass testClass; 4 private final List<Object> parameters; 5 public TestWithParameters(String name, TestClass testClass, 6 List<Object> parameters) { 7 notNull(name, "The name is missing."); 8 notNull(testClass, "The test class is missing."); 9 notNull(parameters, "The parameters are missing."); 10 this.name = name; 11 this.testClass = testClass; 12 this.parameters = unmodifiableList(new ArrayList<Object>(parameters)); 13 } 14 15

BlockJUnit4ClassRunnerWithParameters一般结合Parameterized使用

3.1.3 Theories

Theories允许对无限数据点集的子集测试某种功能。提供一组参数的排列组合值作为待测方法的输入参数。同时注意到在使用Theories这个Runner的时候,待测方法可以拥有输入参数,可以使您的测试更加灵活。

测试代码如下:

1@RunWith(Theories.class) 2public class TheoriesTest { 3 @DataPoints 4 public static String[] tables = {"方桌子", "圆桌子"}; 5 @DataPoints 6 public static int[] counts = {4,6,8}; 7 @Theory 8 public void testMethod(String table, int count){ 9 System.out.println(String.format("一套桌椅有一个%s和%d个椅子", table, count)); 10 } 11} 12 13

运行结果:

图2 Theories测试代码的执行结果

3.1.4 JUnit4

JUnit4是Junit4默认执行器的别名,想要显式地将一个类标记为JUnit4类,应该使用@RunWith(JUnit4.class),而不是,使用@RunWith(BlockJUnit4ClassRunner.class)

3.1.5 Suite

Suite允许您手动构建包含来自许多类的测试的套件.通过Suite.SuiteClasses定义要执行的测试类,一键执行所有的测试类。

测试代码如下:

1@RunWith(Suite.class) 2@Suite.SuiteClasses({Suite_test_a.class,Suite_test_b.class,Suite_test_c.class }) 3public class Suite_main { 4} 5public class Suite_test_a { 6 @Test 7 public void testRun(){ 8 System.out.println("Suite_test_a_running"); 9 } 10} 11public class Suite_test_b { 12 @Test 13 public void testRun(){ 14 System.out.println("Suite_test_b_running"); 15 } 16} 17public class Suite_test_c { 18 @Test 19 public void testRun(){ 20 System.out.println("Suite_test_c_running"); 21 } 22} 23 24

执行结果:

图3 Suite测试代码的执行结果

如结果所示:执行MainSuit时依次执行了Suite_test_a,Suite_test_b,Suite_test_c 的方法,实现了一键执行。

3.1.6 Categories

Categories在给定的一组测试类中,只运行用带有@ inclecategory标注的类别或该类别的子类型标注的类和方法。通过ExcludeCategory过滤类型。

测试代码如下:

1public interface BlackCategory {} 2public interface WhiteCategory {} 3 4 5public class Categories_test_a { 6 @Test 7 @Category(BlackCategory.class) 8 public void testFirst(){ 9 System.out.println("Categories_test_a_testFirst_running"); 10 } 11 @Test 12 @Category(WhiteCategory.class) 13 public void testSecond(){ 14 System.out.println("Categories_test_a_testSecond_running"); 15 } 16} 17 18 19public class Categories_test_b { 20 @Test 21 @Category(WhiteCategory.class) 22 public void testFirst(){ 23 System.out.println("Categories_test_b_testFirst_running"); 24 } 25 @Test 26 @Category(BlackCategory.class) 27 public void testSecond(){ 28 System.out.println("Categories_test_b_testSecond_running"); 29 } 30} 31 32

执行带WhiteCategory的方法

1@RunWith(Categories.class) 2@Categories.IncludeCategory(WhiteCategory.class) 3@Categories.ExcludeCategory(BlackCategory.class) 4@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class }) 5public class Categories_main { 6} 7 8

运行结果:

图4 Categories测试代码WhiteCategory分组执行结果

执行带BlackCategory的方法

1@RunWith(Categories.class) 2@Categories.IncludeCategory(BlackCategory.class) 3@Categories.ExcludeCategory(WhiteCategory.class) 4@Suite.SuiteClasses( { Categories_test_a.class, Categories_test_b.class }) 5public class Categories_main { 6} 7 8

运行结果:

图5 Categories测试代码BlackCategory分组执行结果

如运行结果所示,通过IncludeCategory,ExcludeCategory可以灵活的运行具体的测试类和方法。

3.1.7 Enclosed

Enclosed使用Enclosed运行外部类,内部类中的测试将被运行。 您可以将测试放在内部类中,以便对它们进行分组或共享常量。

测试代码:

1public class EnclosedTest { 2 @Test 3 public void runOutMethou(){ 4 System.out.println("EnclosedTest_runOutMethou_running"); 5 } 6 public static class EnclosedInnerTest { 7 @Test 8 public void runInMethou(){ 9 System.out.println("EnclosedInnerTest_runInMethou_running"); 10 } 11 } 12} 13 14

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

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

使用Enclosed执行器:

1@RunWith(Enclosed.class) 2public class EnclosedTest { 3 @Test 4 public void runOutMethou(){ 5 System.out.println("EnclosedTest_runOutMethou_running"); 6 } 7 public static class EnclosedInnerTest { 8 @Test 9 public void runInMethou(){ 10 System.out.println("EnclosedInnerTest_runInMethou_running"); 11 } 12 } 13} 14 15

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

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

3.1.8 Parameterized

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

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

测试代码:

1@RunWith(Parameterized.class) 2public class ParameterizedTest { 3 @Parameterized.Parameters 4 public static Collection<Object[]> initData(){ 5 return Arrays.asList(new Object[][]{ 6 {"小白",1,"鸡腿"},{"小黑",2,"面包"},{"小红",1,"苹果"} 7 }); 8 } 9 private String name; 10 private int count; 11 private String food; 12 13 14 public ParameterizedTest(String name, int count, String food) { 15 this.name = name; 16 this.count = count; 17 this.food = food; 18 } 19 @Test 20 public void eated(){ 21 System.out.println(String.format("%s中午吃了%d个%s",name,count,food)); 22 } 23} 24 25

运行结果:

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

3.2 JUnit38ClassRunner

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

实现了TestListener接口。

3.3 ErrorReportingRunner

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

1private final List<Throwable> causes; 2 3 4public ErrorReportingRunner(Class<?> testClass, Throwable cause) { 5 if (testClass == null) { 6 throw new NullPointerException("Test class cannot be null"); 7 } 8 this.testClass = testClass; 9 causes = getCauses(cause); 10} 11 12 13 private List<Throwable> getCauses(Throwable cause) { 14 if (cause instanceof InvocationTargetException) { 15 return getCauses(cause.getCause()); 16 } 17 if (cause instanceof InitializationError) { 18 return ((InitializationError) cause).getCauses(); 19 } 20 if (cause instanceof org.junit.internal.runners.InitializationError) { 21 return ((org.junit.internal.runners.InitializationError) cause) 22 .getCauses(); 23 } 24 return Arrays.asList(cause); 25 } 26 27

当junit运行错误时,会抛出ErrorReportingRunner,例如:

1public Runner getRunner() { 2 try { 3 Runner runner = request.getRunner(); 4 fFilter.apply(runner); 5 return runner; 6 } catch (NoTestsRemainException e) { 7 return new ErrorReportingRunner(Filter.class, new Exception(String 8 .format("No tests found matching %s from %s", fFilter 9 .describe(), request.toString()))); 10 } 11} 12 13

3.4 IgnoredClassRunner

IgnoredClassRunner是当测试的方法包含Ignore注解时,会忽略该方法。

1public class IgnoredClassRunner extends Runner { 2 private final Class<?> clazz; 3 public IgnoredClassRunner(Class<?> testClass) { 4 clazz = testClass; 5 } 6 @Override 7 public void run(RunNotifier notifier) { 8 notifier.fireTestIgnored(getDescription()); 9 } 10 @Override 11 public Description getDescription() { 12 return Description.createSuiteDescription(clazz); 13 } 14} 15 16

IgnoredClassRunner的使用

1public class IgnoredBuilder extends RunnerBuilder { 2 @Override 3 public Runner runnerForClass(Class<?> testClass) { 4 if (testClass.getAnnotation(Ignore.class) != null) { 5 return new IgnoredClassRunner(testClass); 6 } 7 return null; 8 } 9} 10 11

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

四、小结

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

作者:京东物流 陈昌浩

来源:京东云开发者社区

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

Demo002 IDEA中Junit单元测试的使用(初级篇)

推荐JUnit视频教程:JUnit—Java单元测试必备工具(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.imooc.com%2Flearn%2F356)。1.基本理论1.1单元测试单元测试又称模块测试,属于白盒测试,是最小单位的测试。模

OpenStack基础知识

针对以前学的内容的一个简单整理1、单元测试工具介绍unittest:是Python的标准库,提供了最基本的单元测试功能,包括单元测试运行器(简称runner)和单元测试框架。项目的单元测试代码的测试类可以继承unittest.TestCase类,那么这个类就能够被runner发现并且执行。同时,unittest.TestCase

Android Studio单元测试

啄木鸟软件测试培训网:www.3testing.com今天简单介绍下Android Studio使用代码进行单元测试。  AndroidStudio默认是支持JUnit和Android单元测试的,市面上大多主要讲解的是JUnit的单元测试,而没有侧重的讲解AndroidStudio针对Android的测试,今天主要介绍A

Python单元测试框架unittest简介

Python单元测试框架,其中的有些部分参照PyUnit。PyUnit是JUnit的python语言版本,它是由KentBeck和ErichGamma创建。JUnit是Kent的Smalltalk测试框架的Java版本。JUnit的每部分都是标准的单元测试框架,很多语言都受他启发而开发自身的单元测试框架。        unittest支持自动化测

JFinal的junit单元测试支持

在jfinal开发中不能直接使用junit单元测试,所以写了以下工具。原理是在运行junit测试方法之前启动jfinal的插件,运行完成后关闭插件。importorg.junit.After;importorg.junit.Before;importcom.jfinal.config.Constants;