Java Annotation原理分析(五) --- JUnit案例分析

 引言:  通过之前的内容,大家基本上对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提取出来,并存放到相应的数据结构里。 具体请看如下的示意图:

        Java Annotation原理分析(五) --- JUnit案例分析_第1张图片

      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>();

     FrameworkMethod:  代表在测试类中的一个方法。               

     FrameworkField:     代表在测试类中的一个Field.

     FrameworkMember: 定义了其中的共性信息。

    那他们是在什么位置被调用的呢?下面我们来简单过一下这个过程。

        Java Annotation原理分析(五) --- JUnit案例分析_第2张图片

                资料来源:    参阅参考文档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是如何被执行的。首先上图,有一个整体的观感。

      Java Annotation原理分析(五) --- JUnit案例分析_第3张图片

       首先来看一下执行的抽象实体: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具体实现类。  

   Java Annotation原理分析(五) --- JUnit案例分析_第4张图片

    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

你可能感兴趣的:(Java技术)