@test注解 无法运行_JUnit4源码分析运行原理

原文:https://my.oschina.net/itblog/blog/1550931

作者:摆渡者

提个问题:Java程序的入口是main方法,而JUnit类中没有main方法,那么JUnit是如何运行的呢?

假如有以下JUnit例子:

import org.junit.Test;public class TestA { @Test public void testAdd() { System.out.println("A.testAdd"); }}

一、在Eclipse中执行JUnit

在Eclipse中右键->Run As->JUnit Test,即可执行这个测试。如果在testAdd方法中打上断点(即第6行),我们来看下程序调用的栈:

626fa100b21e4dc0af809455d780d001

可以看到,这个测试的JVM进程由RemoteTestRunner.main()方法启动,这个类是来自Eclipse的JUnit插件的,真正进入到JUnit的代码是BlockJUnit4ClassRunner.run(RunNotifier)。那么,Eclipse是如何开始调用BlockJUnit4ClassRunner的呢?

adbc551c64a3460691e8916eff76c172

原来,在执行JUnit测试用例之前,Eclipse将会为这个测试类找到合适的Runner。看这段代码,这里给出了5种RunnerBuilder,它们会被按顺序依次遍历,找到一个合适的Runner后即停止:

  1. IgnoreBuilder。检查被测类是否含有@Ignore注解,如果有,则初始化一个IgnoredClassRunner,否则返回null。
  2. AnnotatedBuilder。检查被测类是否含有@RunWith注解,如果有,则用该注解的value初始化一个Runner,否则返回null。
  3. SuiteMethodBuilder。检查被测类是否含有一个叫“suite”的方法,如果有,则初始化一个SuiteMethod(这是JUnit3.8中使用的Runner),否则返回null。
  4. JUnit3Builder。检查被测类是否是TestCase的子类,如果是,则初始化一个JUnit38ClassRunner,否则返回null。
  5. JUnit4Builder。没有检查条件。将初始化一个BlockJUnint4ClassRunner。这也是JUnit4默认的Runner

经过这5步,必然会找到一个Runner,我们这个了例子就会返回BlockJUnit4ClassRunner。接下来,我们看看这个JUnit4ClassRunner中发生了什么。

在上面已经提到,程序会在执行时会调用到BlockJUnit4ClassRunner.run方法,下面是在其父类ParentRunner类中的实现:

@Overridepublic void run(final RunNotifier notifier) {EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription());try {Statement statement = classBlock(notifier);statement.evaluate(); // Would trigger the whole test case execution flow.} catch (AssumptionViolatedException e) {testNotifier.addFailedAssumption(e);} catch (StoppedByUserException e) {throw e;} catch (Throwable e) {testNotifier.addFailure(e);}}

这里主要调用classBlock(RunNotifier)方法(这是一个处理测试类的函数)创建了一个Statement,然后调用Statement.evaluate()完成测试。接下来看看classBlock方法做了些什么:

protected Statement classBlock(final RunNotifier notifier) {Statement statement = childrenInvoker(notifier); // Execute test casesif (!areAllChildrenIgnored()) {statement = withBeforeClasses(statement);statement = withAfterClasses(statement);statement = withClassRules(statement);}return statement;}

主要的逻辑是调用了childrenInvoker生成了一条Statement,而这个Statement的evaluate方法是调用一个runChildren方法(这个方法将在BlockJUnit4ClassRunner.run方法中调用statement.evalueate()方法是被回调):

protected Statement childrenInvoker(final RunNotifier notifier) {return new Statement() {@Overridepublic void evaluate() {runChildren(notifier);}};}

这个runChildren()会遍历这个测试类中的所有测试用例,并为每个用例进一步调用ParentRunner中的抽象方法

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

在classBlock中还可以看到,如果测试类中的不是所有用例都用例(带有@Test注解)都被忽略(带有@Ignore注解)的时候,会把@BeforeClass,@AfterClass,@ClassRule这几个注解对应的信息添加到Statement中。

接下来看BlockJUnit4ClassRunner的runChild的实现:

@Overrideprotected void runChild(final FrameworkMethod method, RunNotifier notifier) {Description description = describeChild(method);if (isIgnored(method)) { // Test if current case is annotated with @Ignorenotifier.fireTestIgnored(description);} else {runLeaf(methodBlock(method), description, notifier);}}

runChild表示运行一个带有@Test注解的测试用例。可以看到,如果这个用例标注了@Ignore(isIgnored方法即是探测该用例是否含有@Ignore注解),则不会执行。否则会调用runLeaf方法来执行,这里面就是调用了Statement的evaluate方法:

protected final void runLeaf(Statement statement, Description description,RunNotifier notifier) {EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);eachNotifier.fireTestStarted();try {statement.evaluate(); // This is a chain call and would handle all steps of a case.} catch (AssumptionViolatedException e) {eachNotifier.addFailedAssumption(e);} catch (Throwable e) {eachNotifier.addFailure(e);} finally {eachNotifier.fireTestFinished();}}

再看看这个methodBlock方法(和前文classBlock类似,这是一个处理测试方法的函数)都做了些什么:

protected Statement methodBlock(FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); statement = possiblyExpectingExceptions(method, test, statement); statement = withPotentialTimeout(method, test, statement); statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); return statement;}

这里面通过使用多次组合的方式,形成了一个复杂的Statement对象:

  1. 先调用methodInvoker创建了一个InvokeMethod(Statement的子类)对象
  2. 如果该用例用@Test(expected="xxx")标注,则用statement组装并返回一个ExpectException(Statement的子类)对象
  3. 如果该用例用@Test(timeout="xxx")标注,则用statement组装并返回一个FailOnTimeout(Statement的子类)对象
  4. 如果该测试类中有方法用@Before标记,则用statement组装并返回一个RunBefores(Statement的子类)对象
  5. 如果该测试类中有方法用@After标记,则用statement组装并返回一个RunAfters(Statement的子类)对象
  6. 如果该用例上有@Rule,则用statement组装并返回一个RunRules(Statement的子类)对象

注意,Statement是一个抽象类,这里提到的InvokeMethod,ExpectException,FailOnTimeout,RunBefores,RunAfters,RunRules这些对象都是Statement类的子类,都实现了evaluate方法,在不同对象的evaluate方法中除了调用子对象的evaluate方法,还有其他的事情。

前文提到,runLeaf方法会调用这个这个statement对象的evaluate方法,这样这个复杂的statement对象会被依次当作各种对象处理并将调用相应对象的evaluate方法,这种链式调用将在合适的时机完成测试用例的所有步骤。

总结

最关键的地方是BlockJUnit4ClassRunner.run()方法:

Statement statement = classBlock(notifier);statement.evaluate(); // Would trigger the whole test case execution flow.

第一句用于生成一个Statement对象,这个对象里面封装了类级别(classBlock)和方法级别(methodBlock)的所有注解相关的信息。

第二句statement.evaluate()方法将触发整个链式调用,将会按照组合的先后顺序倒序执行,比如,生成Statement的顺序为:

Statement statement = childrenInvoker(notifier);statement = withBeforeClasses(statement);statement = withAfterClasses(statement);statement = withClassRules(statement);

那么在执行evaluate方法的时候:

  1. 会先将Statement看做是RunRules并调用其evaluate,
  2. 这会把Statement看做是RunAfters并调用其evaluate,
  3. 这又会把Statement看做是RunBefores并会调用其evalueate,
  4. 这样又会调用childrenInvoker返回的Statement的evaluate,而这是一个回调函数,即会调用runChildren(notifier);
  5. 遍历所有的用例并为每个用例调用runChild(each, notifier);
  6. runChild()方法会调用runLeaf()方法(里面也同样是倒叙执行的过程),以完成一个用例的执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值