JUnit Runner浅析

  • Post author:
  • Post category:其他


这是一篇翻译文章,源链接

https://www.mscharhag.com/java/understanding-junits-runner-architecture


2014.8.15

更新(2020) 这个博文描述的是JUnit 4 runner如何工作和你怎么创建你自己的Junit 4runner。请注意JUnit 5已经发布了好几年了(2017年已经发布)。如果你还在使用JUnit 4,也许你应该考虑将项目升级到JUnit 5,如果你对JUnit 5 感兴趣,可以看我这篇博文:

creating custom extensions for JUnit 5

.

几周前我开始创建一个小小的JUnit Runner。我习得创建一个自定义JUnit Runners是非常简单的。这篇文章里,我将展示JUnit 内部是怎么工作的,如何自定义Runners来修改JUnit的测试执行。



所以,什么是JUnit Runner?

一个JUnit Runner是一个继承了JUnit的抽象类

Runner

的类.Runners是用来运行测试类的。可以通过

@RunWith

注解来指定一个Runner去运行测试。

@RunWith(MyTestRunner.class)
public class MyTestClass {

  @Test
  public void myTest() {
    ..
  }
}

JUnit测试是通过使用类

JUnitCore

开始的。可以通过

命令行

或者使用它的其中一个run()方法来运行测试(IDE的run按钮运行测试就是通过此方法实现的)

JUnitCore.runClasses(MyTestClass.class);

然后JUnitCore使用反射为传入的测试类找到一个合适的Runner。一个步骤便是测试类的

@RunWith

注解。如果没有找到注解将会用默认runner(

BlockJUnit4ClassRunner

) .这个Runner将会实例化并且测试类会传递给这个Runner。接下来的工作便是Runner初始化、运行传入的测试类。



JUnit Runners如何工作?

我们来看一张标准的JUnit Runners继承图

在这里插入图片描述


Runner

是一个非常简单的类,它实现了Describale 接口,它(Runner)有两个抽象方法:

public abstract class Runner implements Describable {
  public abstract Description getDescription();
  public abstract void run(RunNotifier notifier);
}

方法

getDescription

是继承自

Describable

,并且返回了一个

Description

(

源码

),

Description

里是包含的是未来将会被导出、被各种工具使用的信息。例如,你的IDE会用这个信息来展示测试结果。


run()

是一个非常通用的方法,它runs一些东西(比如一个测试类或者测试suite)。我想通常你不会去继承Runner这个类(因为它太抽象了*【译者注:generous,范围太大不好继承,太抽象】*)


ParentRunner

里有了好转,变得具体了一些。

ParentRunner

是那些有多个叶子的Runners的抽象基类。理解这一点很重要,测试被结构化并且已一个层次顺序执行(想下树)。

比如,你有个test suite,它包含了其他test suites.这些test suites也许会包含多个测试类。并且最后每个测试类包含多个测试方法。


ParentRunner

有以下三个抽象方法:

public abstract class ParentRunner<T> extends Runner implements Filterable, Sortable {    
  protected abstract List<T> getChildren();
  protected abstract Description describeChild(T child);
  protected abstract void runChild(T child, RunNotifier notifier);
}


getChildren()

方法里,子类需要返回一个泛型T的列表。然后

ParentRunner

让子类为每个child创建一个

Description

(方法

describeChild

),最后运行每个child(

runChild()

)

现在我们来看两个标准的

ParentRunner

s


BlockJUnit4ClassRunner



Suite



BlockJUnit4ClassRunner

是默认的Runner,所以这个Runner经常用来运行单个测试类。如果你看了

BlockJUnit4ClassRunner

源码你将会注意到:

public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
  @Override
  protected List<FrameworkMethod> getChildren() {
    // scan test class for methonds annotated with @Test
  }
  @Override
  protected Description describeChild(FrameworkMethod method) {
    // create Description based on method name
  }
  @Override
  protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
    if (/* method not annotated with @Ignore */) {
      // run methods annotated with @Before
      // run test method
      // run methods annotated with @After
    }
  }
}

当然这应被极大简化了(【

译者注:就需要简化来帮助我们理清主干

】),但是它展示了

BlockJUnit4ClassRunner

的核心工作。

泛型参数

FrameworkMethod

基本上是对

java.lang.reflect.Method

包装,提供了一些方便的方法。在函数

getChildren()

中,测试类通过反射被扫面来勋在注解了

@Test

的方法。被找到的方法将会被包裹进

FrameworkMethod

对象中然后返回。方法

describeChildren()

以方法名创建了一个

Description

,方法

runChild()

最终运行测试函数。

BlockJUnit4ClassRunner

内部使用了许多protected方法。取决于你想做什么,你可以检查

BlockJUnit4ClassRunner

的方法来覆盖重写。可以在GitHhub上看一下源码

BlockJUnit4ClassRunner on GitHub

.


Suite

Runner被用来创建test suites。Suites是测试的集合(或者其他suites),一个简单的suite定义如下:

@RunWith(Suite.class)
@Suite.SuiteClasses({
  MyJUnitTestClass1.class,
  MyJUnitTestClass2.class,
  MyOtherTestSuite.class
})
public class MyTestSuite {}

当你选择

Suite

Runner 配合

@RunWith

注解时,一个test suite就被创建 了。如果你看了Suite的实现,你会发现它很简单。它只做一个件事:用

@SuiteClasses

注解里定义的类来创建Runner实例。所以

getChildren()

返回一系列

Runners



runChild

委派给对应runner的去执行。



自定义JUnit runners的例子

以上面提供的信息,创建一个你自己的JUnit Runner应该不难(至少我希望如此)。如果你想看一些自定义Runner实现的例子,以下有些参考:



总结

JUnit Runners是高度可自定义的,留给了你修改测试执行过程的空间。修改这个测试流程、集成到IDE、构建服务器这是非常酷的

如果你想做些小的改动,最好看一下

BlockJUnit4Class

runner的protected方法。你应该能找到一个正确的可覆盖的方法。

如果你对Olaester感兴趣,可以看我这篇博文

An alternative approach of writing JUnit tests

.

译者自己的一个Runner

public class BlockedParameterized extends Parameterized {
    /**
     * Only called reflectively. Do not use programmatically.
     *
     * @param klass
     */
    private Class clazz;
    public BlockedParameterized(Class<?> klass) throws Throwable {
        super(klass);
        this.clazz = klass;
    }
    
    @Override
    protected void runChild(Runner runner, final RunNotifier notifier) {
        synchronized (clazz){
            runner.run(notifier);
        }
    }
}