JUnit Runner浅析

6 篇文章 0 订阅

这是一篇翻译文章,源链接 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())
现在我们来看两个标准的ParentRunners
BlockJUnit4ClassRunnerSuite
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 {}

当你选择SuiteRunner 配合@RunWith注解时,一个test suite就被创建 了。如果你看了Suite的实现,你会发现它很简单。它只做一个件事:用@SuiteClasses注解里定义的类来创建Runner实例。所以getChildren()返回一系列RunnersrunChild委派给对应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);
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值