您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Junit执行器Runner探索之旅
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Junit执行器Runner探索之旅
自猿其说Tech
2022-02-14
IP归属:未知
21240浏览
敏捷
测试
敏捷测试
### 1 背景 今年有幸成为敏捷9班-苍龙小队的-SM,在敏捷成熟度评判标准-工程敏捷中要求团队要有自动化单元测试,我想到的方法是定时器加一键单元测试,如何实现一键单元测试呢?@RunWith(Suite.class) 进入了我的视野。 ### 2 RunWith RunWith的注释是当一个类用@RunWith注释或扩展一个用@RunWith注释的类时,JUnit将调用它引用的类来运行该类中的测试,而不是内置到JUnit中的运行器,就是测试类根据指定运行方式进行运行。 代码如下: ```java public @interface RunWith { Class<? extends Runner> value(); } ``` 其中:Runner 就是指定的运行方式。 ### 3 Runner Runner的作用是告诉Junit如何运行一个测试类,它是一个抽象类。通过RunWith 指定具体的实现类,如果不指定默认使用BlockJUnit4ClassRunner,Runner的代码如下: ```java public abstract class Runner implements Describable { public abstract Description getDescription(); public abstract void run(RunNotifier notifier); public int testCount() { return getDescription().testCount(); } } ``` 方法getDescription()是继承自Describable,显示接收器要运行的测试的描述 。 方法run()是为这个运行器执行测试。 参数: notifier-在运行测试时(测试正在启动、结束和失败)将收到事件通知 。 方法testCount()返回运行器要执行的次数。 在junit框架中继承关系如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/bcfc036a-2e8e-4db1-8b23-af12db4240e120220214142116.png) #### 3.1 ParentRunner ParentRunner是一个抽象类,提供了大多数特定于运行器的功能,是经常使用运行器的父节点。实现了Filterable,Sortable接口,可以过滤和排序子对象。 提供了3个抽象方法: ```java 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个抽象方法的实现如下: ```java @Override protected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { runLeaf(methodBlock(method), description, notifier); } } @Override protected 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; } @Override protected List<FrameworkMethod> getChildren() { return computeTestMethods(); } ``` runChild() : - 调用describeChild() - 判断方法是否包含@Ignore注解,有就触发TestIgnored事件通知 - 构造Statement回调,通过methodBlock()构造并装饰测试方法 - 执行测试方法调用statement.evaluate() describeChild() : 对测试方法创建Description并进行缓存 getChildren():返回运行测试的方法。 默认实现返回该类和超类上所有用@Test标注的未重写的方法 ##### 3.1.2 BlockJUnit4ClassRunnerWithParameters BlockJUnit4ClassRunnerWithParameters是一个支持参数的BlockJUnit4ClassRunner。 参数可以通过构造函数注入或注入到带注释的字段中。参数包含名称、测试类和一组参数。 ```java 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(); } ``` 参数代码如下: ```java 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的时候,我们的待测方法可以拥有输入参数,可以使您的测试更加灵活。 测试代码如下: ```java @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)); } } ``` 运行结果: ![](//img1.jcloudcs.com/developer.jdcloud.com/4e4fb6d2-465e-42fb-bf56-b5dbbcd9a57020220214144303.png) ##### 3.1.4 JUnit4 JUnit4是Junit4默认执行器的别名,想要显式地将一个类标记为JUnit4类,应该使用@RunWith(JUnit4.class),而不是,使用@RunWith(BlockJUnit4ClassRunner.class) ##### 3.1.5 Suite Suite允许您手动构建包含来自许多类的测试的套件.通过Suite.SuiteClasses定义要执行的测试类,一键执行所有的测试类。 测试代码如下: ```java @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"); } } ``` 执行结果: ![](//img1.jcloudcs.com/developer.jdcloud.com/66ac23e2-b375-4f06-a902-a61a5cc1799220220214144354.png) 如结果所示:执行MainSuit时依次执行了Suite_test_a,Suite_test_b,Suite_test_c 的方法,实现了一键执行。 ##### 3.1.6 Categories Categories在给定的一组测试类中,只运行用带有@ inclecategory标注的类别或该类别的子类型标注的类和方法。通过ExcludeCategory过滤类型。 测试代码如下: ```java 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的方法 ```java @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 { } ``` 运行结果: ![](//img1.jcloudcs.com/developer.jdcloud.com/0042cb93-e890-400b-ad47-61ad8ff0fa0020220214144503.png) 执行带BlackCategory的方法 ```java @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 { } ``` ![](//img1.jcloudcs.com/developer.jdcloud.com/42b3a6f4-4841-45f2-956c-9bfd3f89b17a20220214144601.png) 如运行结果所示,通过IncludeCategory,ExcludeCategory可以灵活的运行具体的测试类和方法。 ##### 3.1.7 Enclosed Enclosed使用Enclosed运行外部类,内部类中的测试将被运行。 您可以将测试放在内部类中,以便对它们进行分组或共享常量。 测试代码: ```java 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"); } } } ``` 运行结果:没有执行内部类的测试方法。 ![](//img1.jcloudcs.com/developer.jdcloud.com/b527206b-e349-4ad6-ad79-ebc9b2b865b320220214144650.png) 使用Enclosed执行器: ```java @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"); } } } ``` 执行结果:执行了内部类的测试方法。 ![](//img1.jcloudcs.com/developer.jdcloud.com/7480ada9-dac3-4b84-a5eb-bbb62f0ab08120220214144719.png) ##### 3.1.8 Parameterized Parameterized实现参数化测试。 运行参数化的测试类时,会为测试方法和测试数据元素的交叉乘积创建实例。 Parameterized包含一个提供数据的方法,这个方法必须增加Parameters注解,并且这个方法必须是静态static的,并且返回一个集合Collection,Collection中的值长度必须相等。 测试代码: ```java @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)); } } ``` 运行结果: ![](//img1.jcloudcs.com/developer.jdcloud.com/69d93f71-a023-470b-a288-14dcf8af5b3820220214144810.png) #### 3.2 JUnit38ClassRunner JUnit38ClassRunner及其子类是Junit4的内部运行器,有一个内部类OldTestClassAdaptingListener 实现了TestListener接口。 #### 3.3 ErrorReportingRunner ErrorReportingRunner也是Junit4运行错误时抛出的异常,代码如下: ```java 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,例如: ```java 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注解时,会忽略该方法。 ```java 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的使用 ```java public class IgnoredBuilder extends RunnerBuilder { @Override public Runner runnerForClass(Class<?> testClass) { if (testClass.getAnnotation(Ignore.class) != null) { return new IgnoredClassRunner(testClass); } return null; } } ``` 当测试时想忽略某些方法时,可以通过继承IgnoredClassRunner增加特定注解实现。 ### 4 小结 通过RunWith(Runner)让单元测试更灵活,测试场景更丰富,让测试驱动开发可以更好的实现,加快了我们小队敏捷的脚步。 ------------ ###### 自猿其说Tech-京东物流技术发展部 ###### 作者:陈昌浩
原创文章,需联系作者,授权转载
上一篇:全场景流量验证系统
下一篇:京东Spark基于Bloom Filter算法的Runtime Filter Join优化机制
相关文章
浅谈对敏捷的认识
架构研究:研发敏捷与中台架构(论前台bp研发敏捷)
敏捷实践 — 估算
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号