【原创】Junit4详解二:Junit4 Runner以及test case执行顺序和源代码理解

概要:

前一篇文章我们总体介绍了Junit4的用法以及一些简单的测试。之前我有个疑惑,Junit4怎么把一个test case跑起来的,在test case之前和之后我们能做些什么?

Junit4执行顺序是怎样的?带着这些问题,我写了这篇文章,仅供参考,不对之处,盼留言指正,感激万分。前一篇文章:【原创】Junit4详解一:Junit总体介绍

Junit4 runner总体介绍

Junit4编译器在执行TestCase的过程中利用反射机制,以便我们可以对测试的开始过程中进行一些预处理,如读取元数据信息,拦截异常,数据库操作等,由于Junit4默认的测试执行器是:BlockJUnit4ClassRunner,我们以这个执行器粗略地做一些研究。在TestCase执行过程中,主要用到以下类,BlockJUnit4ClassRunner,ParentRunner,Statement,TestRule,Description,RunNotifier,InvokeMethod.以下简单做些解释。

Junit4默认重要类简述,助于理解源代码

1. BlockJUnit4ClassRunner:Junit4的默认测试执行器,它有之前版本的runner同样的行为,也就兼容了之前的runner。但是它是基于Statement,实现更加简单,允许用户在执行工作流中某个合适的点插入用户新的操作,基于这个runner的继承和重用都是可以的,这样能更加具有灵活性。它继承ParentRunner,Junit4要求,执行器的构造函数要把测试类传进来。这个类的实现了ParentRunner的runChild(真正执行测试的方法,每个测试方法都会执行runChild),describeChild(FrameworkMethod method)(用来获取测试类的元数据信息,以及方法和类的信息),一些验证的方法,这些验证方法在ParentRunner构造的时候就会开始验证。另外一个比较重要的方法是:

methodBlock(FrameworkMethod method)

method里面包含当前要测试的方法。

这个方法的作用验证方法能否执行,然后把当前测试类的信息(当前类,测试的方法)传给InvokeMethod,以待后续测试方法的执行,接着获取当前类的元数据信息,保存起来。

2. ParentRunner:Junit4测试执行器的基类,它提供了一个测试器所需要的大部分功能。继承它的类需要实现:

protected abstract List<T> getChildren();

protected abstract Description describeChild(T child);

protected abstract void runChild(T child, RunNotifier notifier);

3. Statement:在运行期时,执行test case前可以插入一些用户动作,它就是描述这些动作的一个类。继承这个类要实现:

    /**

     * Run the action, throwing a {@code Throwable} if anything goes wrong.

     */

     public abstract void evaluate() throws Throwable;

 这个方法,这个方法会先后在ParentRunner.run()和ParentRunner.runLeaf()这两个方法里面调用。另外,我们可以自定义一个Statement,并且实现evaluate()方法。

4. TestRule:TestRule可以描述一个或多个测试方法如何运行和报告信息的接口。在TestRule中可以额外加入一些check,我们可以让一个test case失败/成功,也可以加入一些setup和cleanup要做的事,也可以加入一些log之类的报告信息。总之,跑test case之前的任何事,都可以在里面做。需要实现apply()方法。

    /**

     * Modifies the method-running {@link Statement} to implement this

     * test-running rule.

     * @param base The {@link Statement} to be modified

     * @param description A {@link Description} of the test implemented in {@code base}

     * @return a new statement, which may be the same as {@code base},

     * a wrapper around {@code base}, or a completely new Statement.

     */

     Statement apply(Statement base, Description description);

 这两个类的用法可以见后面的综合例子。

5. Description:存储着当前单个或多个test case的描述信息。这些信息跟逻辑不关,比如元数据信息等。实例化Description用Description.createTestDescription()方法。

 

6. RunNotifier:运行时通知器。执行Runner.run(RunNotifier runNotifier)方法时,需要传一个RunNotifier进去,这个RunNotifier是事件的管理器,它能帮助我们监控测试执行的情况。

 

7. InvokeMethod:最终执行test case里面的测试方法通过这个类来做,这个类会间接调用Method.invoke()方法通知编译器执行@test方法。

Junit4启动test case到结束整个过程概述

  1. Junit4Builder编译器会构造Test运行器,如BlockJUnit4ClassRunner,BlockJUnit4ClassRunner 会通过自己的构造器,把当前测试类传到runner里。
  2. 运行ParentRunner.run()方法,run 方法会获取测试类的Description信息,Description信息会包含测试类的信息,然后执行classBlock(RunNotifier),这个方法获取Statement信息,首先构造一个childrenInvoker,然后在Statement的evaluate()方法调用runChildren()方法,用来真正地执行test方法,这个步骤会等到测试真正执行后开始做。现在是先获取Statement会处理三种注解,@Before,@After,@Rule,把标注这些注解的方法分别放在集合里,以便后面能够处理这些方法。
  3. 准备工作都做好之后,会执行步骤2里从classBlock(RunNotifier)获取到的Statement的evaluate()方法,这个方法用来对Statement来说是开了一个口,用户可以自定义Statement的方法,不过在这里,evaluate()主要是来执行步骤2调用的runChildren()方法。
  4. runChildren()方法的作用是:遍历测试类的所有测试方法(getFilteredChildren),开启线程调度fScheduler,调度线程给每一个测试方法,然后执行Runnable.run()方法,让测试执行器,让测试执行器可以执行测试类的测试方法(runChild)。
  5. 执行测试方法首先会判断方法是否含有@Ignore注解,如果有,那么忽略它的测试。如果没有那么执行runLeaf()方法。这个方法是真正我们执行测试的开始。
  6. RunLeaf(Statement, Description, RunNotifier),首先Statement是有methodBlock(FrameworkMethod)产生,FrameworkMethod 存着当前要执行的测试方法,在这个方法里,用了反射机制。这时候Junit4会构造RunRules会把Statement, Description apply()到MethodRules, TestRule进去,也就是说,我们在外部构造的带有@Rule,@Before, @After等就在这里执行,如:
    @Rule
    public final TestRule testRule = new CommonRule();

     此时,我们就可以在我们新构建的CommonRule类里面,的apply()方法,做一些对test的预处理,比如预处理连接数据库字符串,读取方法上元数据的信息等;

  7. 然后就真正执行RunLeaf(Statement, Description, RunNotifier),通过传进来的参数构建EachTestNotifier,执行fireTestStarted,然后,再打开对话,statement.evaluate(),这个执行过程中,会到InvokeMethod类,然后调用InvokeMethod.evaluate(),最后调用Method.invoke()方法真正地执行了test实体。测试开始了。

备注:就如刚所说的,真正开始测试前,我们可以用apply()进行一些处理,同样地,我们可以在apply()方法中,创建用户Statement,这样就能在evaluate()方法中,做一些操作。如执行脚本,日志等。

元数据的执行顺序

1. 获取元数据信息的顺序

@BeforeClass -> @AfterClass -> ClassRule -> @Test(拿元数据里的expect Exception) -> @Test(拿元数据里的timeout信息) -> @Before -> @After -> @Rule,

2. 注解所标注的方法执行顺序

@ClassRule(TestRule.apply()) -> @BeforeClass -> @Rule(TestRule.apply())  -> @Before -> @Test(test method1) ->@After -> if existed @Rule, then Statement.evaluate() -> @Rule(TestRule.apply()) -> @Before -> @Test(test method2) -> @After -> if existed @Rule, then Statement.evaluate() … -> @AfterClass -> if existed @ClassRule, then Statement.evaluate()

通过Statement.evaluate()执行他们的方法实体,最终执行测试方法的主体。

附录:

TestRule Statement以及注解标识的方法执行顺序代码示例

 

  1 package com.citi.risk.services.credit.facility.impl;
  2 
  3 import java.io.Closeable;
  4 import java.io.IOException;
  5 
  6 import org.junit.After;
  7 import org.junit.AfterClass;
  8 import org.junit.Before;
  9 import org.junit.BeforeClass;
 10 import org.junit.ClassRule;
 11 import org.junit.Rule;
 12 import org.junit.Test;
 13 import org.junit.rules.ExpectedException;
 14 import org.junit.rules.TestRule;
 15 import org.junit.runner.Description;
 16 import org.junit.runners.model.Statement;
 17 
 18 /**
 19  * 执行顺序如下: 默认test方法的执行顺序是随机的,没有顺序
 20  * @ClassRule(TestRule.apply()) -> @BeforeClass -> @Rule(TestRule.apply())  -> @Before 
 21  * -> @Test(test method1) ->@After -> if existed @Rule, then Statement.evaluate() 
 22  * -> @Rule(TestRule.apply()) -> @Before -> @Test(test method2) -> @After 
 23  * -> if existed @Rule, then Statement.evaluate() … -> @AfterClass 
 24  * -> if existed @ClassRule, then Statement.evaluate()
 25  * @author 草原战狼
 26  *
 27  */
 28 public class TestClass {
 29 
 30     @Rule
 31     public ExpectedException expectedException = ExpectedException.none();
 32     
 33     @Rule
 34     public TestRule testRule = new TestRuleValueImpl();
 35     
 36     @Rule
 37     public TestRule testRuleMethod() {
 38         System.out.println();
 39         System.out.println("@Rule Method");
 40         return new TestRuleMethodImpl();
 41     }
 42     @ClassRule
 43     public static TestRule testClassRuleMethod() {
 44         System.out.println("@ClassRule Method");
 45         return new TestRuleMethodImpl();
 46     }
 47 
 48     static class TestRuleValueImpl implements TestRule{
 49         @Override
 50         public Statement apply(Statement base, Description description) {
 51             System.out.println("@Rule property--TestRuleValueImpl execute apply()");
 52             return new StatementValueImpl(base);
 53         }
 54     }
 55 
 56     static class StatementValueImpl extends Statement {
 57         Statement base;
 58         StatementValueImpl(Statement base) {
 59             this.base = base;
 60         }
 61         @Override
 62         public void evaluate() throws Throwable {
 63             System.out.println("@Rule property--StatementValueImpl execute evaluate()");
 64             base.evaluate();
 65         }
 66         
 67     }
 68     static class TestRuleMethodImpl implements TestRule{
 69         @Override
 70         public Statement apply(Statement base, Description description) {
 71             System.out.println("@Rule method--TestRuleMethodImpl execute apply()");
 72             return new StatementMethodImpl(base);
 73         }
 74         
 75     }
 76     
 77     static class StatementMethodImpl extends Statement {
 78         Statement base;
 79         StatementMethodImpl(Statement base) {
 80             this.base = base;
 81         }
 82         @Override
 83         public void evaluate() throws Throwable {
 84             System.out.println("@Rule Method--StatementMethodImpl execute evaluate()");
 85             base.evaluate();
 86         }
 87         
 88     }
 89     static class ExpensiveManagedResource implements Closeable {
 90         @Override
 91         public void close() throws IOException {
 92         }
 93     }
 94 
 95     static class ManagedResource implements Closeable {
 96         @Override
 97         public void close() throws IOException {
 98         }
 99     }
100 
101     @BeforeClass
102     public static void setUpClass() {
103         System.out.println("@BeforeClass setUpClass");
104         myExpensiveManagedResource = new ExpensiveManagedResource();
105     }
106 
107     @AfterClass
108     public static void tearDownClass() throws IOException {
109         System.out.println("@AfterClass tearDownClass");
110         myExpensiveManagedResource.close();
111         myExpensiveManagedResource = null;
112     }
113 
114     private ManagedResource myManagedResource;
115     private static ExpensiveManagedResource myExpensiveManagedResource;
116 
117     private void println(String string) {
118         System.out.println(string);
119     }
120 
121     @Before
122     public void setUp() {
123         this.println("@Before setUp");
124         this.myManagedResource = new ManagedResource();
125     }
126 
127     @After
128     public void tearDown() throws IOException {
129         this.println("@After tearDown");
130         this.myManagedResource.close();
131         this.myManagedResource = null;
132         this.println("   ");
133     }
134 
135     @Test
136     public void test1() {
137         this.println("   @Test test1() begin");
138         this.println("   @Test test1() execute during evaluate()");
139         this.println("   @Test test1() finished");
140     }
141 
142     @Test
143     public void test2() {
144         this.println("   @Test test2() begin");
145         this.println("   @Test test2() execute during evaluate()");
146         this.println("   @Test test2() finished");
147     }
148 
149     @Test
150     public void test3() {
151         this.println("   @Test test3() begin");
152         String hi = "   @Test test3() execute during evaluate()";
153         expectedException.expect(Exception.class);
154         expectedException.expectMessage("ddd");
155         this.println(hi);
156         this.println("   @Test test3() finished.");
157     }
158 }
View TestClass Code

  执行的结果如下:

 

 1 @ClassRule Method
 2 @Rule method--TestRuleMethodImpl execute apply()
 3 @Rule Method--StatementMethodImpl execute evaluate()
 4 @BeforeClass setUpClass  // 预备期结束
 5  //  第一个测试方法开始到结束
 6 @Rule Method
 7 @Rule method--TestRuleMethodImpl execute apply()
 8 @Rule property--TestRuleValueImpl execute apply()
 9 @Rule property--StatementValueImpl execute evaluate()
10 @Rule Method--StatementMethodImpl execute evaluate()
11 @Before setUp
12    @Test test1() begin
13    @Test test1() execute during evaluate()
14    @Test test1() finished
15 @After tearDown
16    
17 // 第二个方法开始到结束,我们可以在apply() 和 evaluate()这两个方法做一些操作。
18 @Rule Method
19 @Rule method--TestRuleMethodImpl execute apply()
20 @Rule property--TestRuleValueImpl execute apply()
21 @Rule property--StatementValueImpl execute evaluate()
22 @Rule Method--StatementMethodImpl execute evaluate()
23 @Before setUp
24    @Test test2() begin
25    @Test test2() execute during evaluate()
26    @Test test2() finished
27 @After tearDown
28    
29 // 第三个方法,这三个方法执行的顺序是随机的,当然Junit4提供了某些排序方式可以处理
30 @Rule Method
31 @Rule method--TestRuleMethodImpl execute apply()
32 @Rule property--TestRuleValueImpl execute apply()
33 @Rule property--StatementValueImpl execute evaluate()
34 @Rule Method--StatementMethodImpl execute evaluate()
35 @Before setUp
36    @Test test3() begin
37    @Test test3() execute during evaluate()
38    @Test test3() finished.
39 @After tearDown
40    
41 @AfterClass tearDownClass

 草原战狼淘宝小店:http://xarxf.taobao.com/ 淘宝搜小矮人鞋坊,主营精致美丽时尚女鞋,为您的白雪公主挑一双哦。谢谢各位博友的支持。

 

====================================================================================

 

======================    以上分析仅代表个人观点,欢迎指正与交流   =========================

 

======================    草原战狼博客,转载请注明出处,万分感谢   =========================

 

====================================================================================

 

 

 

 

转载于:https://www.cnblogs.com/caoyuanzhanlang/p/3534846.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值