引言: 通过之前的内容,大家基本上对Java Annotation已经有了一个深入的了解,在本节,我们将基于JUnit项目的源代码,来分析其Annotation的使用和实现机制。
8.1 JUnit
JUnit是开源领域鼎鼎大名的单元测试框架,从4.x开始,基于Java新的Annotation机制,抛弃了基于继承的单元测试开发方式,转向了基于Annotation标注的单元测试开发。基于Annotation解耦,简单,而且语义也很强大。
主要的标注有: @Test, @Ignore, @Before, @After, @BeforeClass, @AfterClass, @RunWith, @Theory, @DataPoint等等,这里不再一一列出。
这里简要介绍一些主要的若干标签功能描述:
@Test: 用以标注,当前这个方法需要运行,做为test case.
@Before/@After: 在每个测试方法运行之前/之后,需要执行
@BeforeClass/@AfterClass: 在测试类中的测试方法执行之前/之后,只执行一次。
我们将在后续的内容,重点分析这几个Annotation的抽象和执行过程。
8.2 JUnit中Annotation的模型
在JUnit处理Annotation的过程中,首先需要将代码中的Annotation提取出来,并存放到相应的数据结构里。 具体请看如下的示意图:
TestClass: 来查看一下代码中的注释,Wraps a class to be run, providing method validation and annotation searching。封装了一个将要运行的Class,并存储其中的Annotation,以备验证或者查询之用。
在TestClass.java之中定义以下两个数据结构,来存放方法和Field的Annotation信息:
private Map, List> fMethodsForAnnotations = new HashMap, List>();
private Map, List> fFieldsForAnnotations = new HashMap, List>();
FrameworkField: 代表在测试类中的一个Field.
FrameworkMember: 定义了其中的共性信息。
那他们是在什么位置被调用的呢?下面我们来简单过一下这个过程。
资料来源: 参阅参考文档1.
在步骤7,8的时候,会创建适当的Runner。这里的Runner只是一个抽象类。具体的调用步骤如下:
JUnitCore.runrun(Class>... classes) ---> Request.classes(defaultComputer(), classes)
--> new AllDefaultPossibilitiesBuilder() instance, 创建Runner,返回Runner.
--> 如果使用Annotation,则会返回JUnit4Builder实例,参见AllDefaultPossibilitiesBuilder.runForClass().
-->JUnit4Builder唯一的覆盖方法,runForClass()返回BlockJUnit4ClassRunner()实例。
--> 在调用ClassRunner实例之时,会调用在其父类ParentRunner构造函数中的创建TestClass对象。
真正的调用过程,其实就是发生在ParentRunner的构造函数中。
public abstract class ParentRunner extends Runner implements Filterable,
Sortable {
..........
/**
* Constructs a new {@code ParentRunner} that will run {@code @TestClass}
*/
protected ParentRunner(Class> testClass) throws InitializationError {
fTestClass = new TestClass(testClass); //调用位置
validate();
}
........
}
8.3 JUnit中Annotation的执行过程
在这一节我们将来看看,在JUnit框架中, TestCase是如何被执行的。首先上图,有一个整体的观感。
首先来看一下执行的抽象实体:Statement; Represents one or more actions to be taken at runtime in the course of running a JUnit test suite. 就是说,在执行过程中,执行Test的行为的实体。
public abstract class Statement {
/**
* Run the action, throwing a {@code Throwable} if anything goes wrong.
*/
public abstract void evaluate() throws Throwable; //执行方法
}
evaluate()是具体的TestCase的执行主体,分别于InvokeMethod,RunBefores,RunAfters具体实现类。
InvokeMethod: 具体执行单个的TestCase。
public class InvokeMethod extends Statement {
private final FrameworkMethod fTestMethod;
private Object fTarget;
public InvokeMethod(FrameworkMethod testMethod, Object target) {
fTestMethod = testMethod;
fTarget = target;
}
@Override //覆盖evaluate方法
public void evaluate() throws Throwable {
fTestMethod.invokeExplosively(fTarget); //执行当前的TestCase
}
}
RunBefores/RunAfters: 执行一系列的Before/After标注的方法;其中,基于还有指向下一个Statement的引用,用以表示在当前的方法逻辑执行完毕之后,接下来要执行的方法;这里Before/Afters各有不同,这里仅仅列出Before的代码示例。
public class RunBefores extends Statement {
private final Statement fNext; //测试方法
private final Object fTarget; //目标对象
private final List fBefores; //before方法引用
public RunBefores(Statement next, List befores, Object target) {
fNext = next;
fBefores = befores;
fTarget = target;
}
@Override //覆盖evaluate方法
public void evaluate() throws Throwable {
for (FrameworkMethod before : fBefores) {
before.invokeExplosively(fTarget); //优先执行before方法
}
fNext.evaluate(); //执行后续方法
}
}
在抽象类Runner中的run()方法是执行的主要入口方法,在ParentRunner中的runChild()/getChildren()方法是抽象方法,他们在Block4JUnitClassRunner中加以实现。
ParentRunner.run() --> ParentRunner.classBlock() -->ParentRunner.childrenInvoker() --> ParentRunner.runChildren()
----> Block4JUnitClassRunner.runChild().
诸位可以发现,主要执行过程由Runner来完成,具体的单个方法执行于Statement来执行。
8.4 题外话
JUnit框架设计是非常精妙的,其中应用了诸多的设计模式,具体可以参看8.6中的参考资料,值得花点时间看看。 那为什么系统会设计成这个样子,将系统进行这样的抽象和设计,也是我在研究的时候,一直在思考的内容。我会在接下来的另外一篇文章中去专门讨论JUnit的系统设计思路。
8.5 总结
在JUnit中Annotation仅仅是做一个anchor,存在运行状态中;在运行过程中,将这些Annotation提取到相应的数据结构中,然后根据这些数据结构,执行响应的操作。综合而言,在我们将来的系统设计中,如果要应用Annotation,更多的是起到anchor的作用,在运行中,作为执行的标识。
对于框架而言,在诸多功能中使用Annotation,可以极大简化框架使用者的工作量,例如Spring,Hibernate以及JUnit中Annotation的使用,摆脱对于继承的依赖。对于Annotation来说,主要的工作复杂度就在于框架实现代码,需要处理这些Annotation的解析过程,它们由框架设计者来完成。
8.6 参考文档
1. http://my.oschina.net/pangyangyang/blog/153320
2. www.junit.org