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

原创 2014年02月07日 19:27:32

 引言:  通过之前的内容,大家基本上对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<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations = new HashMap<Class<?>, List<FrameworkMethod>>();

    private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations = new HashMap<Class<?>, List<FrameworkField>>();

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

     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<T> 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<FrameworkMethod> fBefores; //before方法引用

    public RunBefores(Statement next, List<FrameworkMethod> 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

基于 JUnit 单元测试的原理及示例

1 简介JUnit是一个Java语言的单元测试框架,它由 Kent Beck 和 Erich Gamma 建立,逐渐成为 xUnit 家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈,...
  • qq_35246620
  • qq_35246620
  • 2017-01-19 23:05:17
  • 3478

Junit原理详解一

Interface:Test 整个测试的的基础接口 Method 1: abstract int countTestCases() 这个方法主要是用来计算要运行的test case的数量的。 Me...
  • yuebao1991
  • yuebao1991
  • 2015-12-30 16:41:30
  • 2338

Junit的原理介绍(转)

1. Introduction In an earlier article (see Test Infected: Programmers Love Writing Tests, Java Repor...
  • qiuliangjun
  • qiuliangjun
  • 2005-05-24 11:49:00
  • 4997

Junit原理详解二

之前我们看到了Test接口里面的run方法有个TestResult的参数,不错,这个类就是用来收集测试结果的,是收集TestSuite的运行结果,所以一般情况下,一个TestSuite对应一个Test...
  • yuebao1991
  • yuebao1991
  • 2015-12-30 16:46:21
  • 469

Spring+junit4 实现注解测试原理解析。

背景:   我们在使用Spring集成的项目进行开发的时候,需要在不依赖web容器启动的时候去进行单元测试,而Spring为我们提供了相应单元测试框架,在spring 的org.spring...
  • zhouzhiwengang
  • zhouzhiwengang
  • 2016-11-07 17:46:54
  • 2246

Junit原理详解三

我们以一个非常简单的TestCalculator类为例,只有一个测试方法: Public class TestCalculator extends TestCase {       Public...
  • yuebao1991
  • yuebao1991
  • 2015-12-30 16:54:03
  • 738

与JUnit干杯 ---- JUnit框架的架构原理以及应用

与JUnit干杯 ---- JUnit框架的架构原理以及应用安全合理而富有可扩展性与可读性的测试代码进入了对象中,它就象一棵大数那样支撑起了整个工程。1. PURPOSEu       理解系统测试与...
  • zrhk
  • zrhk
  • 2004-12-11 22:27:00
  • 1900

JUnit4框架代码解析

JUnit4的代码到底是怎么实现的。
  • michaellufhl
  • michaellufhl
  • 2010-11-19 22:39:00
  • 8962

JUnit框架功能详细——JUnit学习(一)

目录[-] 一个简单的JUnit JUnit的生命周期 使用@RunWith注解 未完待续 是著名的单元测试框架,在JUnit4中所有的测试用例采用@Annotati...
  • ggj20ss
  • ggj20ss
  • 2015-07-15 18:01:33
  • 1185

JUnit3原理分析二(框架结构与源码分析)

在上一篇我们介绍了JUnit3的简单应用,这里我们会再分析JUnit3框架介绍以及部分源代码分析。源码下载地址:http://download.csdn.net/download/wuhenhcp/3...
  • chenzhentao
  • chenzhentao
  • 2016-01-28 16:50:14
  • 771
收藏助手
不良信息举报
您举报文章:Java Annotation原理分析(五) --- JUnit案例分析
举报原因:
原因补充:

(最多只允许输入30个字)