深入JUnit源码之Runner

简介:
初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑。不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉JUnit这个框架还是值得看的,里面有许多不错的设计思想在,更何况它是Kent Beck和Erich Gamma这样的大师写的。。。。。

写在前面的话

不知道是因为第一份工作的影响还是受在博客园上看到的那句“源代码里没有秘密”的影响,总之,近来对很多框架的源码都很感兴趣,拿到一个都想看看。其实自从学习Java以来也看过不少了,从刚开始接触的Tomcat,到Struts2,再到入职当前这份工作后看的Log4J,Commons Validator和SpringBatch,可惜除了Commons Validator的源码都看完了,其他的框架源码都半途而废了,并且Commons Validator看完后,也没有什么记录下来,所以基本上都可以忽略,其实当时也知道看完要有总结和记录才会有真正的收获,然而还是因为各种事情而没有做。所以这次看JUnit,发奋看完后一定要做总结,并记录。

对于JUnit,一直以为很简单,可以很快的看完,就当练练手,然而当我真正看完后,才发现JUnit其实并不简单,内部框架提供了很多特性,平时都没用过,而且也没见过,比如使用RunWith注解以指定特定的Runner、Rule、使用hamcrest框架的Assert.assertThat()方法等。其实JUnit提供的这些特性在一些特殊场合很有用处,然而对于单元测试,很多人却因为太忙或者因为嫌烦,维护麻烦等各种理由而尽量避免。我也是一样,虽然我一直承认单元测试非常重要,它不仅仅可以在前期影响设计、而且可以为后期的一些重构或者bug修复提供信心,但是要我真正的认真的为一个模块写单元测试,也感觉有点厌烦,再加上当前项目又是一个很少写单元测试的环境,对这方面坚持就松懈了。

后来,想系统的看一下项目的源码,可是代码量太多,也没有比较明确的模块分工,没有比较完善的单元测试,再加上当前项目主要是基于文件对数据的处理,没有看到数据流,只是看代码的话,感觉晕头转向的,所以就想着补一些单元测试以使自己可以更好的理解项目代码。问题是项目是跑在Linux Server上的,项目在开发的过程中没有完全考虑支持跨平台,同时有些操作内存消耗也很大,所以并不是所有的代码都可以在本地跑,等等,总之各种原因吧,我开始打算写一个可以在Linux下用跑测试代码的工具。然后悲剧的发现除了会使用eclipse中的JUnit,其实我对JUnit一无所知。所以有些时候工具虽然能提供我们方便,但是它也隐藏了内部细节,最后我们自以为已经很了解某些东西了,其实离开了工具,我们一无所知。

最后加个注释,这篇文章所有的代码是基于JUnit4.10的,今天我发现这个版本和之前的版本(JUnit4.4)的代码还是有比较大的差别的。本系列最后可能会涉及到一点和JUnit之前版本相关的信息,不过这个就要看有没有这个时间了。L

深入JUnit源码之Runner

Runner是JUnit的核心,它封装了一个测试类中所有的测试方法,如BlockJUnit4ClassRunner,或者是多个Runner,如Suite;在运行过程中,遍历并运行所有测试方法(可以通过Filter和Sorter控制是否要执行某些测试方法以及执行测试方法的顺序)。

在使用JUnit时,可能最常用的几个注解就是@BeforeClass、@AfterClass、@Before、@After、@Test、@Ignore了,这几个注解所表达的意思相信很多人都很熟悉了,并且从它们的名字中也可以略知一二。这几个注解,除了@Test标记了哪个方法为测试方法,该方法必须为public,无返回,不带参;@Ignore标明某个方法即使有@Test的注解,也会忽略不运行,如JUnit文档中解释,在某些情况下,我们可能想临时的不想执行某些测试方法,除了将该测试方法整个注释掉,JUnit为我们提供了@Ignore注解,此时即使某方法包含@Test注解,该方法也不会作为测试方法执行,@Ignore还可以注解在类上,当一个类存在@Ignore注解时,该类所有的方法都不会被认为是测试方法;而剩下的四个注解则是JUnit为在测试类运行时的不同切面提供了切入点,如@BeforeClass和@AfterClass注解分别在测试类运行时前后各提供了一个切入点,这两个注解必须使用在public,静态,无返回,不带参的方法中,可以为每种注解指定多个方法,多个方法的执行顺序依赖与Java的反射机制,因而对一种注解的多个方法,在实际中不应该存在顺序依赖,为一种注解写多个方法的情况应该很少;而@Before和@After注解则是在每个测试方法的运行前后各提供了一个切入点,这两个注解必须使用在public,无返回,不带参的方法中。同@BeforeClass和@AfterClass,同一种注解可以注释多个方法,他们的执行顺序也依赖于反射机制,因而不能对顺序有依赖。更直观的,如下图所示。

加点不完全相关的,这事实上是一种AOP思想的实现。自从知道AOP后,一直很喜欢这个想法,它事实上也是一种分层的思想。一个request从一个管道流进,经过层层处理后从另一个管道流出,在管道的流动过程中,每一层都对自己实现的功能做一些处理,如安全验证、运行时间记录、进入管道后打开某个链接出去之前关闭该链接、异常记录等等相对独立的功能都可以抽取出来到一个层中,从而在实际编码业务逻辑过程中可以专注于业务,而不用管这些不怎么相关但有必须有的逻辑,不仅是代码的模块分层更加清晰,减轻程序员的负担,还提高了程序的安全性,因为这样就可以部分避免有些事情必须要做容易忘了尴尬。AOP的思想最出名的应该是Spring中提供的支持了,但是我个人更喜欢Struts2通过Interceptor提供的AOP实现,这是题外话。

一个简单的测试例子

为了更加清晰的了解JUnit的一些行为,我们先来看一下如下的一个测试例子:

 1  public  class CoreJUnit4SampleTest {
 2     @BeforeClass
 3      public  static  void beforeClass() {
 4         System.out.println("beforeClass() method executed.");
 5         System.out.println();
 6     }
 7     @BeforeClass
 8      public  static  void beforeClass2() {
 9         System.out.println("beforeClass2() method executed.");
10         System.out.println();
11     }
12     @AfterClass
13      public  static  void afterClass() {
14         System.out.println("afterClass() method executed.");
15         System.out.println();
16     }
17     @Before
18      public  void before() {
19         System.out.println("before() method executed.");
20     }
21     @After
22      public  void after() {
23         System.out.println("after() method executed");
24     }
25     @Test
26      public  void testSucceeded() {
27         System.out.println("testSucceeded() method executed.");
28     }
29     @Test
30     @Ignore
31      public  void testIgnore() {
32         System.out.println("testIgnore() method executed.");
33     }
34     @Test
35      public  void testFailed() {
36         System.out.println("testFailed() method executed.");
37          throw  new RuntimeException("Throw delibrately ");
38     }
39     @Test
40      public  void testAssumptionFailed() {
41         System.out.println("testAssumptionFailed() method executed.");
42         Assume.assumeThat(0, Is.is(1));
43     }
44     @Test
45      public  void testFilteredOut() {
46         System.out.println("testFilteredOut() method executed.");
47     }
48 }
这个是一个简单的测试类,内部实现基本上只是打印,以确定测试方法的运行位置。该测试方法包括两个@BeforeClass注解的方法,以测试多个@BeforeClass注解时他们的运行顺序问题;@AfterClass、@Before、@After注解的方法各一个,以测试他们的运行位置问题;在多个@Test注解的测试方法中,testSucceeded()测试方法用于测试通过时RunLinstener的运行结果,testIgnore()测试方法测试@Ignore注解对测试结果的影响,testFailed()方法测试在测试方法抛异常时RunListener的运行结果,testAssumptionFailed()方法测试在测试方法断言出错时RunListener的运行结果,testFilteredOut()方法测试Filter的功能。在JUnit中,BlockJUnitClassRunner是其最核心的Runner,它对一个只包含测试方法的测试类的运行做了封装,并且它还实现了Filterable和Sortable的接口,因而支持Filter和Sorter,为了更全面的展现JUnit提供的功能,我在这个例子中还加入了Filter和Sorter的测试,其中实现了一个Filter类:MethodNameFilter,在构造时指定要过滤掉的方法名,Runner在运行之前调用filter()方法,以过滤掉这些方法。对于Sorter只实现了一个按字母序排列的Comparator,它会以参数形式传递给Sorter构造函数,以决定测试方法的顺序,Runner在运行之前调用sort()方法,以按指定的顺序排列测试方法:
 1  public  class MethodNameFilter  extends Filter {
 2      private  final Set<String> excludedMethods =  new HashSet<String>();
 3      public MethodNameFilter(String  excludedMethods) {
 4          for(String method : excludedMethods) {
 5              this.excludedMethods.add(method);
 6         }
 7     }
 8     @Override
 9      public  boolean shouldRun(Description description) {
10         String methodName = description.getMethodName();
11          if(excludedMethods.contains(methodName)) {
12              return  false;
13         }
14          return  true;
15     }
16     @Override
17      public String describe() {
18          return  this.getClass().getSimpleName() + "-excluded methods: " + 
19                 excludedMethods;
20     }
21 }
22  public  class AlphabetComparator  implements Comparator<Description> {
23     @Override
24      public  int compare(Description desc1, Description desc2) {
25          return desc1.getMethodName().compareTo(desc2.getMethodName());
26     }
27 }
由于本文主要讲解JUnit中的Runner的实现,因而在这个例子中,我将直接构造BlockJUnit4ClassRunner实例,以运行上述的测试类:
 1  public   class  BlockJUnit4ClassRunnerExecutor {
 2       public   static   void  main(String[] args) {
 3          RunNotifier notifier  =   new  RunNotifier();            
 4          Result result  =   new  Result();
 5          notifier.addFirstListener(result.createListener());
 6          notifier.addListener( new  LogRunListener());
 7          
 8          Runner runner  =   null ;
 9           try  {
10              runner  =   new  BlockJUnit4ClassRunner(CoreJUnit4SampleTest. class );
11               try  {
12                  ((BlockJUnit4ClassRunner)runner).filter( new  MethodNameFilter( " testFilteredOut " ));
13              }  catch  (NoTestsRemainException e) {
14                  System.out.println( " All methods are been filtered out " );
15                   return ;
16              }
17              ((BlockJUnit4ClassRunner)runner).sort( new  Sorter( new  AlphabetComparator()));
18          }  catch  (Throwable e) {
19              runner  =   new  ErrorReportingRunner(CoreJUnit4SampleTest. class , e);
20          }
21          notifier.fireTestRunStarted(runner.getDescription());
22          runner.run(notifier);
23          notifier.fireTestRunFinished(result);
24      }
25 
JUnit 会在 Runner 运行之前通过 RunNotifier 发布 testRunStarted 事件表示 JUnit 运行开始,并在 Runner 运行结束之后通过 RunNotifier 发布 testRunFinished 时间,表示 JUnit 运行结束。在 Runner 运行过程中,在每个测试方法开始前也会通过 RunNotifier 发布 testStarted 事件,在测试方法结束后发布 testFinished 事件(不管该测试方法通过还是未通过),若测试失败,则发布 testFailure 事件,若测试方法因调用 Assume 类中的方法失败(这种失败不认为是测试失败),则会发布 testAssumptionFailure 事件,若遇到一个 Ignore 测试方法,发布 testIgnored 事件。我们可以再 RunNotifier 中加入要注册的 Listener (事件接收器),如上例所示,为了测试,这个例子编写的 LogRunListener 代码如下:
 1  public  class LogRunListener  extends RunListener {
 2      public  void testRunStarted(Description description)  throws Exception {
 4         println("==>JUnit4 started with description: \n" + description);
 5         println();
 6     }
 7      public  void testRunFinished(Result result)  throws Exception {
 8         println("==>JUnit4 finished with result: \n" + describe(result));
 9     }
10      public  void testStarted(Description description)  throws Exception{
11         println("==>Test method started with description: " + description);
13     }
14      public  void testFinished(Description description)  throws Exception {
16         println("==>Test method finished with description: " + description);
18         println();
19     }
20      public  void testFailure(Failure failure)  throws Exception {
21         println("==>Test method failed with failure: " + failure);
22     }
23      public  void testAssumptionFailure(Failure failure) {
24         println("==>Test method assumption failed with failure: " + failure);
26     }
27      public  void testIgnored(Description description)  throws Exception {
28         println("==>Test method ignored with description: " + description);
30         println();
31     }
32      private String describe(Result result) {
33         StringBuilder builder =  new StringBuilder();
34         builder.append("\tFailureCount: " + result.getFailureCount())
35                .append("\n");
36         builder.append("\tIgnoreCount: " + result.getIgnoreCount())
37                .append("\n");
38         builder.append("\tRunCount: " + result.getRunCount())
39                .append("\n");;
40         builder.append("\tRunTime: " + result.getRunTime())
41                .append("\n");
42         builder.append("\tFailures: " + result.getFailures())
43                .append("\n");;
44          return builder.toString();
45     }    
46      private  void println() {
47         System.out.println();
48     }
49      private  void println(String content) {
50         System.out.println(content);
51     }
52 }
最后这个例子的运行结果(从上面的分析中,这个运行结果应该已经很清晰了,只是有两点需要注意,其一,@Ignore注解的方法会被忽略不执行,包括@Before、@After注解的方法,也不会触发该testStarted事件,但是在Result会记录被忽略的测试方法数,而被Filter过滤掉的方法(testFilteredOut())则不会有任何记录;其二,事件的触发都是在@Before注解之前或@After注解之后,事实上,如果测试方法中包含Rule字段的话,也会在Rule执行之前或之后,这就是JUnit抽象出的Statement提供的特性,这是一个非常好的设计,这个特性将会在下一节:深入JUnit源码之Statement中讲解):
==>JUnit4 started with description: 
levin.blog.junit.sample.simple.CoreJUnit4SampleTest

beforeClass2() method executed.

beforeClass() method executed.

==>Test method started with description: testAssumptionFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)
before() method executed.
testAssumptionFailed() method executed.
after() method executed
==>Test method assumption failed with failure: testAssumptionFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest): got: <0>, expected: is <1>
==>Test method finished with description: testAssumptionFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)

==>Test method started with description: testFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)
before() method executed.
testFailed() method executed.
after() method executed
==>Test method failed with failure: testFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest): Throw delibrately
==>Test method finished with description: testFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)

==>Test method ignored with description: testIgnore(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)

==>Test method started with description: testSucceeded(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)
before() method executed.
testSucceeded() method executed.
after() method executed
==>Test method finished with description: testSucceeded(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)

afterClass() method executed.

==>JUnit4 finished with result: 
    FailureCount: 1
    IgnoreCount: 1
    RunCount: 3
    RunTime: 36
    Failures: [testFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest): Throw delibrately ]

深入Runner和其周边支持类的源码

从上面这个例子中,我们已经知道JUnit的核心功能以及不同注解方法执行的顺序问题,然而既然本文是关注内部源码的,因而接下来就要讨论如何实现上述的这些功能。

所谓面向对象中的类即是对某些事物或行为进行抽象和封装,从而实现某些功能,一个类可以看成是一个模块,它一般包含数据和行为,并提供给外界一定的接口,多个类之间通过各自的接口与外界交互,从而形成一个大的系统,有点类似分治的算法。在分治算法中最重要的是找到正确的方法以将问题划分成各个区间,并最后使用一定的规则将各个区间解决的问题连结在一起,以解决整个系统的问题。在面向对象中同样要找到一个正确的方法将系统的问题划分成各个小模块,用类来封装,并定义类的接口以使各个类之间可以交互连结以形成一个大的系统。在实现中,我们也经常遇到某些事物是不同的,但是他们有一些共同的属性或行为,在面向对象中对这个情况的处理是将相同的属性或行为抽象成一个父类,而由各自的子类继承父类提供各自不同的属性和行为。然而有些时候,某些事物他们都具有某种行为,但是这些行为的结果却是不同的,对这种情况,面向对象则采用多态的方式支持这种需求,即在父类中定义行为,在子类中重写行为。在面向对象设计中,如何设计系统的类结构,包括类的继承结构、类之间的交互接口等问题了是面向对象设计的核心问题。

JUnit将测试类中的方法(测试方法以及切面方法,@BeforeClass、@AfterClass、@Before、@After等注解的方法)抽象成FrameworkMethod类模块,和测试相关的字段(由@Rule和@ClassRule注解的字段)抽象成FrameworkField类模块,而这两个类具有一些共同的行为,如获取在其之上的所有注解类、是否被其他相关成员隐藏等,因而JUnit将这些共同行为提取到父类FrameworkMember中。对每个测试类,JUnit使用TestClass类来封装,TestClass类以测试类的Class实例为构造函数的参数,它收集测试类中所有JUnit识别的注解方法(@BeforeClass、@AfterClass、@Before、@After、@Test、@Ignore)和注解字段(@Rule、@ClassRule),以供其他类查询。在每一次运行中,JUnit使用Runner对其封装,它可以是只包含一个测试类的BlockJUnit4ClassRunner,也可以是包含多个Runner的Suite(这有点类似Composite设计模式,以测试方法为叶子节点,以Runner为包含叶子节点的节点将依次JUnit运行过程中的所有测试方法组成一棵树);这两个Runner都继承自ParentRunner。ParentRunner表达它是以一个在树中具有子节点的节点,实现了Filterable接口和Sortable接口,以实现filter和sort的功能,ParentRunner继承自Runner。Runner是一个更高层次的抽象,目前在JUnit4中表达该Runner只是在执行树中的没有子节点的Runner节点,如ErrorReportingRunner、IgnoredClassRunner等;在JUnit3中不是采用注解的方式取得测试方法,为了兼容性,JUnit4中也提供了JUnit38ClassRunner类以完成兼容性的工作。Runner实现了Discribable接口,以表明可以通过Description来描述一个Runner;Description主要是对Runner和测试方法的描述,有点类似toString()的味道。Runner的每一次执行过程,如切面方法的执行、测试方法的执行、Rule字段的执行等,JUnit都将其封装在Statement类中,一个测试方法的执行可能存在多个Statement,他们形成链结构,这是一个我个人非常喜欢的设计,就像Servlet中的Filter、Struts2的Interceptor的设计类似,也是对AOP的主要实现,这个内容将在另一节中介绍。Runner在每个测试方法执行过程中都会通过RunNotifier类发布一些事件,如测试方法执行开始、结束、出错、忽略等事件,RunNotifier是Runner对所有事件处理的封装,我们可以通过它注册RunListener的事件响应类,如上例的LogRunListener的注册。到这里,我们基本上已经介绍完了JUnit的所有的核心类简单的功能和一些简单的交互,那么我们来看一下他们的类结构图吧:


  Description类和Discribable接口的实现

看完类结构图,那么我们再来看一下源码吧。由于DescriptionJUnit中应用广泛,又是相对独立于功能的,因而将从Description开始:

如上文所说,DescriptionJUnit中对Runner和测试方法的描述,它包含三个字段:

1  private   final  ArrayList < Description >  fChildren;
2  private   final  String fDisplayName;    
3  private   final Annotation[] fAnnotations;

 displayName对测试方法的格式为:<methodName>(<className>),如:

testFailed(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)

Runner来说,displayName一般为Runner所封装的测试类,然而对没有根类的Suite,该值为”null”annotations字段为测试方法或测试类上所具有的所有注解类。children对测试方法来说为空,对Runner来说,表达Runner内部所有的测试方法的DescriptionRunnerDescription。作为JUnit的用户,除非自定义Runner,其他的,我们一般都是通过注册自己的RunListener来实现自己想要的统计和信息提示工作,而在Listener中并没有直接暴露给我们Runner或者是测试类的实例,它是通过提供Description实例的方式来获取我们需要的信息。Description提供以下的接口供我们使用:

 1  public  String getDisplayName();
 2  public  ArrayList < Description >  getChildren();
 3  public   boolean  isSuite();
 4  public   boolean  isTest();
 5  public   int  testCount();
 6  public   < extends  Annotation >  T getAnnotation(Class < T >  annotationType);
 7  public  Collection < Annotation >  getAnnotations();
 8  public  Class <?>  getTestClass();
 9  public  String getClassName();
10  public String getMethodName();

Runner实现了Disacribable接口,该接口只包含一个方法:

1  public   interface  Describable {
2       public   abstract  Description getDescription();
3 }

该方法在ParentRunner中创建一个Description,并遍历当前Runner下的Children,并将这些childDescription添加到Description中最后返回:

 1  @Override
 2  public  Description getDescription() {
 3      Description description =  Description.createSuiteDescription(
 4  getName(),    getRunnerAnnotations());
 5       for  (T child : getFilteredChildren())
 6          description.addChild(describeChild(child));
 7       return  description;
 8  }
 9  BlockJUnit4ClassRunner:
10  @Override
11  protected  Description describeChild(FrameworkMethod method) {
12       return  Description.createTestDescription(
13  getTestClass().getJavaClass(),
14                  testName(method), method.getAnnotations());
15  }
16  Suite:
17  @Override
18  protected  Description describeChild(Runner child) {
19       return  child.getDescription();
20  }
21 

RunNotifier类和RunListener类对测试方法运行事件的发布

RunListenerJUnit提供的自定义对测试运行方法统计的接口,JUnit用户可以继承RunListener类,在JUnit运行开始、结束以及每一个测试方法的运行开始、结束、测试失败以及假设出错等情况下加入一些自己的逻辑,如统计整个JUnit运行的时间(这个在Result中已经实现了)、每个运行方法的时间、运行最后有多少方法成功,多少失败等,如上例中的LogRunListenerRunListener定义了如下接口:

 1  public   class  RunListener {
 2       public   void  testRunStarted(Description description)  throws  Exception {
 4      }
 5       public   void  testRunFinished(Result result)  throws  Exception {
 6      }
 7       public   void  testStarted(Description description)  throws  Exception {
 8      }
 9       public   void  testFinished(Description description)  throws  Exception {
11      }
12       public   void  testFailure(Failure failure)  throws  Exception {
13      }
14       public   void  testAssumptionFailure(Failure failure) {
15      }
16       public   void  testIgnored(Description description)  throws  Exception {
17      }
18 }

这个类需要注意的是:1. testFinished()不管测试方法是成功还是失败,这个方法总是会被调用;2. 在测试方法中跑出AssumptionViolatedException并不认为是测试失败,一般在测试方法中调用Assume类中的方法而失败,会跑该异常,在JUnit的默认实现中,对这些方法只是简单的忽略,并发布testAssumptionFailure()事件,并不认为该方法测试失败,自定义的Runner可以改变这个行为;3. 当我们需要自己操作Runner实例是,Result的信息需要自己手动的注册Result中定义的Listener,不然Result中的信息并不会填写正确,如上例中的做法:

1  Result result  =   new  Result();
2 notifier.addFirstListener(result.createListener());

在实现时,所有事件响应函数提供给我们有三种信息:DescriptionResultFailure。其中Description已经在上一小节中介绍过了它所具有的信息,这里不再重复。对于Result,前段提到过只有它提供的Listener后才会取到正确的信息,它包含的信息有:总共执行的测试方法数、忽略的测试方法数、以及所有在测试过程中抛出的异常列表、整个测试过程的执行时间等,Result实例在JUnit运行结束时传入testRunFinished()事件方法中,以帮助我们做一些测试方法执行结果的统计信息,事实上,我感觉这些信息很多时候还是不够的,需要我们在自己的RunListener中自己做一些信息记录。Failure类则记录了测试方法在测试失败时抛出的异常以及该测试方法对应的Description实例,当在构建Runner过程中出现异常,Failure也会用于描述测试类,如上例中,如果beforeClass()方法不是静态的话,在初始化Runner时就会出错,此时的运行结果如下:

==> JUnit4 started with description: 
levin.blog.junit.sample.simple.CoreJUnit4SampleTest

==> Test method started with description: initializationError(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)
==> Test method failed with failure: initializationError(levin.blog.junit.sample.simple.CoreJUnit4SampleTest): Method beforeClass() should be  static
==> Test method finished with description: initializationError(levin.blog.junit.sample.simple.CoreJUnit4SampleTest)

==> JUnit4 finished with result: 
    FailureCount: 
1
    IgnoreCount: 
0
    RunCount: 
1
    RunTime: 
3
    Failures: [initializationError(levin.blog.junit.sample.simple.CoreJUnit4SampleTest): Method beforeClass() should be 
static ]

Runner中的run()方法需要传入RunNotifier实例,它是对RunListener的封装,我们可以向其注册多个RunListener,当Runner需要发布某个事件时,就会通过它来代理,它则会遍历所有已注册的RunListener,并运行相应的事件。当运行某个RunListener抛异常时,它会首先将这个RunListener移除,并发布测试失败事件,在该事件中的Description为一个名为“Test mechanism”的Description,因为此时是注册的事件处理器失败,无法获知一个Description实例:

 1  private   abstract   class  SafeNotifier {
 2       void  run() {
 3           synchronized  (fListeners) {
 4               for  (Iterator < RunListener >  all =  fListeners.iterator(); 
 5                              all.hasNext();)
 6                   try  {
 7                      notifyListener(all.next());
 8                  }  catch  (Exception e) {
 9                      all.remove();  //  Remove the offending listener  first to avoid an infinite loop
11                      fireTestFailure( new  Failure(
12                              Description.TEST_MECHANISM, e));
13                  }
14          }
15      }
16       abstract   protected   void  notifyListener(RunListener each)  throws  Exception;
18 }

RunNotifier类中还有一个pleaseStop()方法从而可以在测试中途停止整个测试过程,其实现时在发布testStarted事件时,如果发现pleaseStop字段已经为true,则抛出StoppedByUserException,当Runner接收到该异常后,将该异常直接抛出。

事实上,ParentRunner在发布事件时,并不直接和RunNotifier打交道,而是会用EachTestNotifier类对其进行封装,该类只是对MultipleFailureException做了处理,其他只是一个简单的代理。在遇到MultipleFailureException,它会遍历内部每个Exception,并对每个Exception发布testFailure事件。当在运行@After注解的方法时抛出多个异常类(比如测试方法已经抛出异常了,@After注解方法中又抛出异常或者存在多个@After注解方法,并多个@After注解方法抛出了多个异常),此时就会构造一个MultipleFailureException,这种设计可能是出于对防止测试方法中的Exception@After注解方法中的Exception覆盖的问题引入的。

FrameworkMethodFrameworkField

FrameworkMethod是对Java反射中Method的封装,它提供了对方法的验证、调用以及处理子类方法隐藏父类方法问题,其主要提供的接口如下:

 1  public  Method getMethod();
 2  public  Object invokeExplosively( final  Object target,  final  Object params);
 3  public  String getName();
 4  public   void  validatePublicVoidNoArg( boolean  isStatic, List < Throwable >  errors);
 5  public   void  validatePublicVoid( boolean  isStatic, List < Throwable >  errors) 
 6  public   void  validateNoTypeParametersOnArgs(List < Throwable >  errors);
 7  @Override
 8  public   boolean  isShadowedBy(FrameworkMethod other);
 9  @Override
10  public  Annotation[] getAnnotations();
11  public   < extends  Annotation >  T getAnnotation(Class < T > annotationType);

其中isShadowedBy()方法用于处理子类方法隐藏父类方法的问题,JUnit支持测试类存在继承关系,并且会遍历所有父类的测试方法,但是如果子类的方法隐藏了父类的方法,则父类的方法不会被执行。这里的隐藏是指当子类的方法名、所有参数类型相同时,不管是静态方法还是非静态方法,都会被隐藏。其实现如下:

 1  public   boolean  isShadowedBy(FrameworkMethod other) {
 2       if  ( ! other.getName().equals(getName()))
 3           return   false ;
 4       if  (other.getParameterTypes().length  !=  getParameterTypes().length)
 5           return   false ;
 6       for  ( int  i =   0 ; i  <  other.getParameterTypes().length; i ++ )
 7           if  ( ! other.getParameterTypes()[i].equals( getParameterTypes()[i]))
 9               return   false ;
10       return   true ;
11 }

类似FrameworkMethodFrameworkField是对Java反射中的Field的封装,它提供了对字段取值、处理隐藏父类字段的问题,其主要提供的接口如下:

 1  public  String getName();
 2  @Override
 3  public  Annotation[] getAnnotations();
 4  public   boolean  isPublic();
 5  @Override
 6  public   boolean  isShadowedBy(FrameworkField otherMember) {
 7       return  otherMember.getName().equals(getName());
 8  }
 9  public   boolean  isStatic();
10  public  Field getField();
11  public  Class <?>  getType();
12  public  Object get(Object target) 
13  throws IllegalArgumentException, IllegalAccessException;

在处理隐藏问题是,它只是判断如果父类中某个字段的名和子类中某个字段的名字相同,则父类中的字段不会被JUnit处理,即被隐藏。事实上,关于隐藏的问题,只是对JUnit识别的方法和字段有效,即有JUnit相关注解的方法字段,其他方法并不受影响(事实上也是不可能受到影响)。

TestClass

TestClass 是对 Java Class 类的封装,在 TestClass 构造过程中,它会收集所有传入 Class 实例中具有注释的字段和方法,它会搜索类的所有继承结构。因而 TestClass 的成员如下:
1  private   final  Class <?>  fClass;
2  private  Map < Class <?> , List < FrameworkMethod >>  fMethodsForAnnotations;
3  private  Map < Class <?> , List < FrameworkField >> fFieldsForAnnotations;
并且这些成员在TestClass 构造完后即已经初始化完成。在所有注解的收集过程中,它对@Before @BeforeClass 有一个特殊的处理,即父类中@Before @BeforeClass 的注解方法总是在之类之前,从而保证父类的@Before @BeforeClass 的注解方法会在子类的这些注解方法之前执行。而对@After @AfterClass 的注解方法没有做处理,因而保持了之类的@After @AfterClass 注解方法会在父类的这些注解方法之前执行。这段特殊逻辑是通过以下代码实现的(在addToAnnotationLists() 方法中,其中runsTopToBottom() 方法形象的表达了这个意思):
1  if  (runsTopToBottom(type))
2      members.add( 0 , member);
3  else
4     members.add(member);

在获得测试类中所有注解的方法和字段的Map后,TestClass提供了对Annotation对应的方法和字段的查询接口:

1  public  List < FrameworkMethod >  getAnnotatedMethods(
2              Class <?   extends  Annotation >  annotationClass);
3  public  List < FrameworkField >  getAnnotatedFields(
4              Class <?   extends  Annotation >  annotationClass);
5  public   < T >  List < T >  getAnnotatedFieldValues(Object test,
6              Class <?   extends  Annotation >  annotationClass, 
7  Class < T > valueClass);
以及测试类相关的信息,如 Class 实例、名字、测试类中具有的 Annotation 等:
1  public  Class <?>  getJavaClass();
2  public  String getName();
3  public  Constructor <?>  getOnlyConstructor();
4  public  Annotation[] getAnnotations();
5  public   boolean isANonStaticInnerClass();

RunnerParentRunnerBlockJUnit4ClassRunner

RunnerJUnit中对所有Runner的抽象,它只包括三个方法:

1  public   abstract   class  Runner  implements  Describable {
2       public   abstract  Description getDescription();
3       public   abstract   void  run(RunNotifier notifier);
4       public   int  testCount() {
5           return  getDescription().testCount();
6      }
7 }

 其中getDescription()的实现已经在Description相关的小节中做过计算了,不在重复,testCount()方法很简单,也已经实现了,因而本节主要介绍run()方法的运行过程,而该方法的实现则是在ParentRunner中。

用ParentRunner命名这个Runner是想表达这是一个在测试方法树中具有子节点的节点,它的子节点可以是测试方法(BlockJUnit4ClassRunner)或者Runner(Suite)。ParentRunner以测试类Class实例作为构造函数的参数,在构造函数内部用TestClass对测试类进行封装,并对一些那些规定的方法做验证:

1.       @BeforeClass和@AfterClass注解的方法必须是public,void,static,无参

1  validatePublicVoidNoArgMethods(BeforeClass. class true , errors);
2  validatePublicVoidNoArgMethods(AfterClass. class true, errors);

 2.       对有@ClassRule修饰的字段,必须是public,static,并且该字段的实例必须是实现了TestRule接口的。为了兼容性,也可以实现MethodRule接口。

在验证过程中,ParentRunner通过一个List<Throwable>收集所有的验证错误信息,如果存在错误信息,则验证不通过,ParentRunner将抛出一个InitializationError的Exception,该Exception中包含了所有的错误信息,一般在这种情况下,会创建一个ErrorReportingRunner返回以处理验证出错的问题,该Runner将在下面的小节中详细介绍。

在Runner构造完成后,就可以调用run()方法运行该Runner了:

 1  @Override
 2  public   void  run( final  RunNotifier notifier) {
 3      EachTestNotifier testNotifier =   new  EachTestNotifier(notifier,  getDescription());
 5       try  {
 6          Statement statement =  classBlock(notifier);
 7          statement.evaluate();
 8      }  catch  (AssumptionViolatedException e) {
 9          testNotifier.fireTestIgnored();
10      }  catch  (StoppedByUserException e) {
11           throw  e;
12      }  catch  (Throwable e) {
13          testNotifier.addFailure(e);
14      }
15 }

该方法的核心是构造一个Statement实例,然后调用该实例的evaluate()方法。在JUnit中,Statement是一个类链表,一个Runner的执行过程就是这个Statement类链表的执行过程。ParentRunnerStatement类链表的构造主要是对测试类级别的构造,如@BeforeClass@AfterClass@ClassRule等执行Statement

 1  protected  Statement classBlock( final  RunNotifier notifier) {
 2      Statement statement =  childrenInvoker(notifier);
 3      statement =  withBeforeClasses(statement);
 4      statement =  withAfterClasses(statement);
 5      statement =  withClassRules(statement);
 6       return  statement;
 7  }
 8  protected  Statement childrenInvoker( final  RunNotifier notifier) {
 9       return   new  Statement() {
10          @Override
11           public   void  evaluate() {
12              runChildren(notifier);
13          }
14      };
15  }
16  private   void  runChildren( final  RunNotifier notifier) {
17       for  ( final  T each : getFilteredChildren())
18          fScheduler.schedule( new  Runnable() {
19               public   void  run() {
20                  ParentRunner. this .runChild(each, notifier);
21              }
22          });
23      fScheduler.finished();
24  }
25  private  List < T >  getFilteredChildren() {
26       if  (fFilteredChildren  ==   null )
27          fFilteredChildren  =   new  ArrayList < T > (getChildren());
28       return  fFilteredChildren;
29  }
30  protected   abstract  List < T >  getChildren();
31  protected   abstract   void runChild(T child, RunNotifier notifier);

其中getChildren()runChild()方法由子类实现。

 1  protected  Statement withBeforeClasses(Statement statement) {
 2      List < FrameworkMethod >  befores =  fTestClass
 3              .getAnnotatedMethods(BeforeClass. class );
 4       return  befores.isEmpty()  ?  statement :
 5           new  RunBefores(statement, befores,  null );
 6  }
 7  protected  Statement withAfterClasses(Statement statement) {
 8      List < FrameworkMethod >  afters =  fTestClass
 9              .getAnnotatedMethods(AfterClass. class );
10       return  afters.isEmpty()  ?  statement : 
11           new  RunAfters(statement, afters,  null );
12  }
13  private  Statement withClassRules(Statement statement) {
14      List < TestRule >  classRules =  classRules();
15       return  classRules.isEmpty()  ?  statement :
16           new  RunRules(statement, classRules, getDescription());
17  }
18  protected  List < TestRule >  classRules() {
19       return  fTestClass.getAnnotatedFieldValues( null
20          ClassRule. class , TestRule. class );
21 }

关于Statement将在下一节中详细讲,不过这里从Statement的构造过程可以看出Rule的执行要先于@BeforeClass注解方法或晚于@AfterClass注解方法。

ParentRunner关于执行方法的实现中,还有一个对每个测试方法Statement链的执行框架的实现,其主要功能是加入事件发布和对异常的处理逻辑,从这里的Statement实例是一个测试方法的运行整体,它包括了@Before注解方法、@After注解方法以及@Rule注解字段的TestRule实例的运行过程,因而testStarted事件的发布是在@Before方法运行之前,而testFinished事件的发布是在@After方法运行之后:

 1  protected   final   void  runLeaf(Statement statement,  Description description,    RunNotifier notifier) {
 3      EachTestNotifier eachNotifier =   new  EachTestNotifier( notifier, description);
 5      eachNotifier.fireTestStarted();
 6       try  {
 7          statement.evaluate();
 8      }  catch  (AssumptionViolatedException e) {
 9          eachNotifier.addFailedAssumption(e);
10      }  catch  (Throwable e) {
11          eachNotifier.addFailure(e);
12      }  finally  {
13          eachNotifier.fireTestFinished();
14      }
15 }

ParentRunner还实现Filterable接口和Sortable接口,不过这里很奇怪的实现时为什么sorter要保存成字段,感觉这个完全可以通过方法参数实现,而filteredChildren字段的实现方式在多线程环境中运行的话貌似也会出问题。Filter类主要提供一个shouldRun()接口以判断传入的Description是否可以运行,若否,则将该DescriptionParentRunner中移除;否则,将该Filter应用到该child中,以处理child可能是Filterable实例(ParentRunner)的问题,从而以递归、先根遍历的方式遍历测试实例树上的所有节点,若一个父节点的所有子节点都被过滤了,则抛出NoTestRemainException

 1  public   void  filter(Filter filter)  throws  NoTestsRemainException {
 2       for  (Iterator < T >  iter  =  getFilteredChildren().iterator(); 
 3                          iter.hasNext(); ) {
 4          T each  =  iter.next();
 5           if  (shouldRun(filter, each))
 6               try  {
 7                  filter.apply(each);
 8              }  catch  (NoTestsRemainException e) {
 9                  iter.remove();
10              }
11           else
12              iter.remove();
13      }
14       if  (getFilteredChildren().isEmpty()) {
15           throw   new  NoTestsRemainException();
16      }
17  }
18  private   boolean  shouldRun(Filter filter, T each) {
19       return  filter.shouldRun(describeChild(each));
20 }

JUnit中提供几种默认实现的Filter1. ALL不做任何过滤;2. matchMethodDescription只保留某个指定的测试方法。

 1  public   static  Filter ALL =   new  Filter() {
 2      @Override
 3       public   boolean  shouldRun(Description description) {
 4           return   true ;
 5      }
 6      @Override
 7       public  String describe() {
 8           return   " all tests " ;
 9      }
10      @Override
11       public   void  apply(Object child)  throws  NoTestsRemainException {
12           //  因为不会做任何过滤行为,因而不需要应用到子节点中
13      }
14      @Override
15       public  Filter intersect(Filter second) {
16           return  second;  //  因为本身没有任何过滤行为,所以可以直接返回传入的Filter
17      }
18  };
19  public   static  Filter matchMethodDescription( final  Description desiredDescription) {
21       return   new  Filter() {
22          @Override
23           public   boolean  shouldRun(Description description) {
24               if  (description.isTest())
25                   return  desiredDescription.equals(description);
26               //  explicitly check if any children want to run
27               for  (Description each : description.getChildren())
28                   if  (shouldRun(each))
29                       return   true ;
30               return   false ;                    
31          }
32          @Override
33           public  String describe() {
34               return  String.format( " Method %s " desiredDescription.getDisplayName());
36          }
37      };
38 }

Sorter类实现Comparator接口,ParentRunner通过其compare()方法构造和ParentRunner子节点相关的Comparator实例,并采用后根递归遍历的方式对测试方法树中的所有测试方法进行排序,因而这里只会排序同一个节点下的所有子节点,而节点之间的顺序不会受影响。

 1  public   void  sort(Sorter sorter) {
 2      fSorter =  sorter;
 3       for  (T each : getFilteredChildren())
 4          sortChild(each);
 5      Collections.sort(getFilteredChildren(), comparator());
 6  }
 7  private  Comparator <?   super  T >  comparator() {
 8       return   new  Comparator < T > () {
 9           public   int  compare(T o1, T o2) {
10               return  fSorter.compare(describeChild(o1),  describeChild(o2));
12          }
13      };
14 }

最后ParentRunner还提供了一个RunnerScheduler的接口字段,以控制JUnit测试方法的执行过程,我们可以注入一个使用多线程方式运行每个测试方法的RunnerScheduler,不过从代码上看,貌似JUnit对多线程的支持并不好,所以这个接口的扩展目前来看我还找不到什么用途:

1  public   interface  RunnerScheduler {
2       void  schedule(Runnable childStatement);
3       void  finished();
4 }

BlockJUnit4ClassRunner节点下所有的子节点都是测试方法,它是JUnit中运行测试方法的核心Runner,它实现了ParentRunner中没有实现的几个方法:

  1  @Override
  2  protected  List < FrameworkMethod >  getChildren() {
  3       return  computeTestMethods();
  4  }
  5  protected  List < FrameworkMethod >  computeTestMethods() {
  6       return  getTestClass().getAnnotatedMethods(Test. class );
  7  }
  8  @Override
  9  protected   void  runChild( final  FrameworkMethod method,  RunNotifier notifier) {
 11      Description description =  describeChild(method);
 12       if  (method.getAnnotation(Ignore. class !=   null ) {
 13          notifier.fireTestIgnored(description);
 14      }  else  {
 15          runLeaf(methodBlock(method), description, notifier);
 16      }
 17  }
 18  protected  Statement methodBlock(FrameworkMethod method) {
 19      Object test;
 20       try  {
 21          test =   new  ReflectiveCallable() {
 22              @Override
 23               protected  Object runReflectiveCall()  throws  Throwable {
 24                   return  createTest();
 25              }
 26          }.run();
 27      }  catch  (Throwable e) {
 28           return   new  Fail(e);
 29      }
 30      Statement statement =  methodInvoker(method, test);
 31      statement =  possiblyExpectingExceptions(method, test, statement);
 32      statement =  withPotentialTimeout(method, test, statement);
 33      statement =  withBefores(method, test, statement);
 34      statement =  withAfters(method, test, statement);
 35      statement =  withRules(method, test, statement);
 36       return  statement;
 37  // 这个方法的实现可以看出每个测试方法运行时都会重新创建一个新的测试类实例,这也可能是@BeforeClass、AfterClass、@ClassRule需要静态的原因吧,因为静态的话,每次类实例的重新创建对其结果都不会有影响。
 38  另,从这里对Statement的构建顺序,JUnit对TestRule的运行也要在@Before注解方法之前或@After注解方法之后
 39  protected  Object createTest()  throws  Exception {
 40       return  getTestClass().getOnlyConstructor().newInstance();
 41  }
 42  protected  Statement methodInvoker(FrameworkMethod method, Object test) {
 43       return   new  InvokeMethod(method, test);
 44  }
 45  protected  Statement possiblyExpectingExceptions(FrameworkMethod  method, object test, Statement next) {
 47      Test annotation =  method.getAnnotation(Test. class );
 48       return  expectsException(annotation)  ?   new  ExpectException(next,
 49              getExpectedException(annotation)) : next;
 50  }
 51  private  Class <?   extends  Throwable >  getExpectedException(Test annotation) {
 52       if  (annotation  ==   null   ||  annotation.expected()  ==  None. class )
 53           return   null ;
 54       else
 55           return  annotation.expected();
 56  }
 57  private   boolean  expectsException(Test annotation) {
 58       return  getExpectedException(annotation)  !=   null ;
 59  }
 60  protected  Statement withPotentialTimeout(FrameworkMethod method,
 61          Object test, Statement next) {
 62       long  timeout =  getTimeout(method.getAnnotation(Test. class ));
 63       return  timeout  >   0   ?   new  FailOnTimeout(next, timeout) : next;
 64  }
 65  private   long  getTimeout(Test annotation) {
 66       if  (annotation  ==   null )
 67           return   0 ;
 68       return  annotation.timeout();
 69  }
 70  protected  Statement withBefores(FrameworkMethod method, Object target,
 71          Statement statement) {
 72      List < FrameworkMethod >  befores =  getTestClass().getAnnotatedMethods(
 73              Before. class );
 74       return  befores.isEmpty()  ?  statement :  new  RunBefores(statement,
 75              befores, target);
 76  }
 77  protected  Statement withAfters(FrameworkMethod method, Object target,
 78          Statement statement) {
 79      List < FrameworkMethod >  afters =  getTestClass().getAnnotatedMethods(
 80              After. class );
 81      return  afters.isEmpty()  ?  statement :  new  RunAfters(statement, 
 82              afters, target);
 83  }
 84  private  Statement withRules(FrameworkMethod method, Object target,
 85          Statement statement) {
 86      Statement result =  statement;
 87      result =  withMethodRules(method, target, result);
 88      result =  withTestRules(method, target, result);
 89       return  result;
 90  }
 91  private  Statement withMethodRules(FrameworkMethod method, Object target,
 92          Statement result) {
 93      List < TestRule >  testRules =  getTestRules(target);
 94       for  (org.junit.rules.MethodRule each : getMethodRules(target))
 95           if  ( !  testRules.contains(each))
 96              result =  each.apply(result, method, target);
 97       return  result;
 98  }
 99  private  List < org.junit.rules.MethodRule >  getMethodRules(Object target) {
100       return  rules(target);
101  }
102  protected  List < org.junit.rules.MethodRule >  rules(Object target) {
103       return  getTestClass().getAnnotatedFieldValues(target, Rule. class ,
104              org.junit.rules.MethodRule. class );
105  }
106  private  Statement withTestRules(FrameworkMethod method, Object target,
107          Statement statement) {
108      List < TestRule >  testRules =  getTestRules(target);
109       return  testRules.isEmpty()  ?  statement :
110           new  RunRules(statement, testRules, describeChild(method));
111  }
112  protected  List < TestRule >  getTestRules(Object target) {
113       return  getTestClass().getAnnotatedFieldValues(target,
114              Rule. class , TestRule. class );
115 }

在验证方面,BlockJUnit4ClassRunner也加入了一些和自己相关的验证:  

1  @Override
2  protected   void  collectInitializationErrors(List < Throwable >  errors) {
3       super .collectInitializationErrors(errors);
4      validateNoNonStaticInnerClass(errors);
5      validateConstructor(errors);
6      validateInstanceMethods(errors);
7      validateFields(errors);
8 }

1.       如果测试类是一个类的内部类,那么该测试类必须是静态的:

1  protected   void  validateNoNonStaticInnerClass(List < Throwable >  errors) {
2       if  (getTestClass().isANonStaticInnerClass()) {
3          String gripe =   " The inner class  "   +  getTestClass().getName()
4                   +   "  is not static. " ;
5          errors.add( new  Exception(gripe));
6      }
7 }

2.       测试类的构造函数必须有且仅有一个无参的构造函数(事实上关于只有一个构造函数的验证在构造TestClass实例的时候已经做了,因而这里真正起作用的知识对无参的验证):

 1  protected   void  validateConstructor(List < Throwable >  errors) {
 2      validateOnlyOneConstructor(errors);
 3      validateZeroArgConstructor(errors);
 4  }
 5  protected   void  validateOnlyOneConstructor(List < Throwable >  errors) {
 6       if  ( ! hasOneConstructor()) {
 7          String gripe =   " Test class should have exactly one public constructor " ;
 8          errors.add( new  Exception(gripe));
 9      }
10  }
11  protected   void  validateZeroArgConstructor(List < Throwable >  errors) {
12       if  ( ! getTestClass().isANonStaticInnerClass()
13               &&  hasOneConstructor()
14               &&  (getTestClass().getOnlyConstructor(). getParameterTypes().length  !=   0 )) {
16          String gripe =   " Test class should have exactly one public zero-argument constructor " ;
17          errors.add( new  Exception(gripe));
18      }
19  }
20  private   boolean  hasOneConstructor() {
21       return  getTestClass().getJavaClass().getConstructors().length  ==   1 ;
22 }

3.       @Before@After@Test注解的方法必须是publicvoid,非静态,不带参数:

 1  protected   void  validateInstanceMethods(List < Throwable >  errors) {
 2      validatePublicVoidNoArgMethods(After. class false , errors);
 3      validatePublicVoidNoArgMethods(Before. class false , errors);
 4      validateTestMethods(errors);
 5       if  (computeTestMethods().size()  ==   0 )
 6          errors.add( new  Exception( " No runnable methods " ));
 7  }
 8  protected   void  validateTestMethods(List < Throwable >  errors) {
 9      validatePublicVoidNoArgMethods(Test. class false , errors);
10 }

4.       带有@Rule注解的字段必须是public,非静态,实现了TestRule接口或MethodRule接口。

1  private   void  validateFields(List < Throwable >  errors) {
2      RULE_VALIDATOR.validate(getTestClass(), errors);
3 }

JUnit核心执行的序列图

写了好几天,终于把JUnit核心的执行所有代码分析完了,不过发现写的那么细,很多东西其实很难表达,因而贴了很多代码,感觉写的挺乱的,所以画一张序列图吧,感觉很多时候还是图的表达效果更好一些,要我看那么一大段的问题,也感觉挺烦的。


SuiteParameterized

Suite是其子节点是RunnerRunner,其内部保存了一个RunnerListSuite的功能实现很简单,因为它将大部分的方法代理给了其内部Runner实例:

 1  private   final  List < Runner >  fRunners;
 2  @Override
 3  protected  List < Runner >  getChildren() {
 4       return  fRunners;
 5  }
 6  @Override
 7  protected  Description describeChild(Runner child) {
 8       return  child.getDescription();
 9  }
10  @Override
11  protected   void  runChild(Runner runner,  final  RunNotifier notifier) {
12      runner.run(notifier);
13 }

Suite重点在于如何构建一个SuiteSuite提供两个构造函数:

1.       提供一个带SuiteClasses注解的类,所有测试类由SuiteClasses指定,而klass类作为这个Suite的根类。此时一般所有的测试类是klass的内部类,因而可以通过@RunWith指定运行klass类的RunnerSuite,然后用SuiteClasses注解指定可以作为测试类的类。

 1  public  Suite(Class <?>  klass, RunnerBuilder builder)  throws  InitializationError {
 3       this (builder, klass, getAnnotatedClasses(klass));
 4  }
 5  protected  Suite(RunnerBuilder builder, Class <?>  klass,  Class <?> [] suiteClasses)  throws  InitializationError {
 7       this (klass, builder.runners(klass, suiteClasses));
 8  }
 9  @Retention(RetentionPolicy.RUNTIME)
10  @Target(ElementType.TYPE)
11  @Inherited
12  public  @ interface  SuiteClasses {
13       public  Class <?> [] value();
14  }
15  private   static  Class <?> [] getAnnotatedClasses(Class <?>  klass)  throws  InitializationError {
17      SuiteClasses annotation =  klass.getAnnotation(SuiteClasses. class );
18       if  (annotation  ==   null )
19           throw   new  InitializationError(String.format( " class '%s' must have a SuiteClasses annotation " klass.getName()));
22       return  annotation.value();
23 }

 2.       在有些情况下,我们需要运行多个没有相关的测试类,此时这些测试类没有一个公共的根类的Suite,则需要使用一下的构造函数(此时SuitegetName()返回”null”,即其DescriptiondisplayName的值为”null”,关于RunnerBuilder将会在后面的文章中介绍):

 1  public  Suite(RunnerBuilder builder, Class <?> [] classes)  throws  InitializationError {
 3       this ( null , builder.runners( null , classes));
 4  }
 5  protected  Suite(Class <?>  klass, List < Runner >  runners)  throws  InitializationError {
 7       super (klass);
 8      fRunners  =  runners;
 9 }

Parameterized继承自Suite,它类似BlockJUnit4ClassRunner是对一个测试类的运行过程的封装,但是它支持在测试类中定义一个获得参数数组的一个列表,从而可以为构造该测试类提供不同的参数值以进行多次测试。因而Parameterized这个Runner其实就是根据参数数组列表的个数创建多个基于该测试类的Runner,并运行所有这些Runner中测试方法。

由于这个测试类的构造函数是带参的,而BlockJUnit4ClassRunner则限制其测试类必须是不带参的,因而Parameterized需要创建自己的Runner,它集成自BlockJUnit4ClassRunner,除了构造函数的差别,Parameterized的根节点是该测试类本身,它处理@BeforeClass@AfterClass@ClassRule的注解问题,因而其子节点的Runner不需要重新对其做处理了,因而在集成的Runner中应该避免这些方法、字段重复执行。

 1  private   class  TestClassRunnerForParameters  extends  BlockJUnit4ClassRunner {
 3       private   final   int  fParameterSetNumber;
 4       private   final  List < Object[] >  fParameterList;
 5      TestClassRunnerForParameters(Class <?>  type,  List < Object[] >  parameterList,  int  i)  throws  InitializationError {
 8           super (type);
 9          fParameterList =  parameterList;
10          fParameterSetNumber =  i;  // 参数数组列表中的位置
11      }
12      @Override
13       public  Object createTest()  throws  Exception {
14           return  getTestClass().getOnlyConstructor().newInstance(
15                  computeParams()); // 带参创建测试类实例
16      }
17       private  Object[] computeParams()  throws  Exception {
18           try  {
19               return  fParameterList.get(fParameterSetNumber);
20          }  catch  (ClassCastException e) {
21               throw   new  Exception(String.format(
22                       " %s.%s() must return a Collection of arrays. " ,
23                      getTestClass().getName(), getParametersMethod(
24                              getTestClass()).getName()));
25          }
26      }
27      @Override
28       protected  String getName() {
29           return  String.format( " [%s] " , fParameterSetNumber);
30      }
31      @Override
32       protected  String testName( final  FrameworkMethod method) {
33           return  String.format( " %s[%s] " , method.getName(),
34                  fParameterSetNumber);
35      }
36      @Override
37       protected   void  validateConstructor(List < Throwable >  errors) {
38          validateOnlyOneConstructor(errors); // 去除不带参构造函数验证
39      }
40      @Override
41       protected  Statement classBlock(RunNotifier notifier) {
42           return  childrenInvoker(notifier); // 不处理@BeforeClass、
43  // @AfterClass、@ClassRule等注解
44      }
45      @Override
46       protected  Annotation[] getRunnerAnnotations() {
47           return   new  Annotation[ 0 ]; // 去除测试类的Annotation,这些应该在 Parameterized中处理
49      }
50 }

ParameterizedSuite的行为改变只是在创建Runner集合的过程,Parameterized通过类中查找@Parameters注解的静态方法获得参数数组列表,并更具这个参数数组列表创建TestClassRunnerForParameters的集合:

 1  @Retention(RetentionPolicy.RUNTIME)
 2  @Target(ElementType.METHOD)
 3  public   static  @ interface  Parameters {
 4  }
 5  public  Parameterized(Class <?>  klass)  throws  Throwable {
 6       super (klass, Collections. < Runner > emptyList());
 7      List < Object[] >  parametersList =  getParametersList(getTestClass());
 8       for  ( int  i =   0 ; i  <  parametersList.size(); i ++ )
 9          runners.add( new  TestClassRunnerForParameters(getTestClass(). getJavaClass(), parametersList, i));
11  }
12  private  List < Object[] >  getParametersList(TestClass klass)
13           throws  Throwable {
14       return  (List < Object[] > ) getParametersMethod(klass). invokeExplosively( null );
16  }
17  private  FrameworkMethod getParametersMethod(TestClass testClass)
18           throws  Exception {
19      List < FrameworkMethod >  methods =  testClass .getAnnotatedMethods(Parameters. class );
21       for  (FrameworkMethod each : methods) {
22           int  modifiers =  each.getMethod().getModifiers();
23           if  (Modifier.isStatic(modifiers)  &&  
24                  Modifier.isPublic(modifiers))
25               return  each;
26      }
27       throw   new  Exception( " No public static parameters method on class  +  testClass.getName());
29 }

一个简单的Parameterized测试类如下,不过如果在Eclipse中单独的运行testEqual()测试方法会出错,因为EclipseFilter传入的方法名为testEqual,而在Parameterized中这个方法名已经被改写成testEqual[i]了,因而在Filter过程中,所有的测试方法都被过滤掉了,此时会抛NoTestRemainException,而在Eclipse中出现的则是InitializationErrorException,这里也是EclipseJUnit的插件没有做好,它并没有给出Failure类的详细信息:

 1  public   class  ParameterizedTest {
 2      @Parameters
 3        public   static  List < Object[] >  data() {
 4            return  Arrays.asList( new  Object[][] {
 5                   {  0 0  }, {  1 1  }, {  2 1  }, {  3 2  }, 
 6                   4 3  }, {  5 5  }, {  6 8  }
 7           });
 8       }
 9       @BeforeClass
10        public   static   void  beforeClass() {
11           System.out.println( " .testing. " );
12       }
13        private   final   int  input;
14        private   final   int  expected;
15        public  ParameterizedTest( int  input,  int  expected) {
16            this .input  =  input;
17            this .expected  =  expected;
18       }
19       @Test
20        public   void  testEqual() {
21           Assert.assertEquals(expected, compute(input));
22       }
23        public   int  compute( int  input) {
24            if (input  ==   0   ||  input  ==   1 ) {
25                return  input;
26           }
27            if (input  ==   2 ) {
28                return   1 ;
29           }
30            return  compute(input  - 1 +  compute(input  -   2 );
31       }
32 }

ErrorReportingRunnerIgnoredClassRunner

关于JUnit4,还剩下最后两个Runner,分别是ErrorReportingRunnerIgnoredClassRunner,为了编程模型的统一(这是一个非常好的设计想法,将各种变化都封装在Runner中),JUnit中即使一个Runner实例创建失败或是该测试类有@Ignored的注解,在这两种情况中,JUnit分别通过ErrorReportingRunnerIgnoredClassRunner去表达。在ErrorReportingRunner中,为每个Exception发布测试失败的信息;IgnoredClassRunner则只是发布testIgnored事件:

 1  public   class  ErrorReportingRunner  extends  Runner {
 2       private   final  List < Throwable >  fCauses;
 3       private   final  Class <?>  fTestClass;
 4       public  ErrorReportingRunner(Class <?>  testClass, Throwable cause) {
 5          fTestClass =  testClass;
 6          fCauses =  getCauses(cause);
 7      }
 8      @Override
 9       public  Description getDescription() {
10          Description description =  Description.createSuiteDescription( fTestClass);
12           for  (Throwable each : fCauses)
13              description.addChild(describeCause(each));
14           return  description;
15      }
16      @Override
17       public   void  run(RunNotifier notifier) {
18           for  (Throwable each : fCauses)
19              runCause(each, notifier);
20      }
21      @SuppressWarnings( " deprecation " )
22       private  List < Throwable >  getCauses(Throwable cause) {
23           if  (cause  instanceof  InvocationTargetException)
24               return  getCauses(cause.getCause());
25           if  (cause  instanceof  InitializationError)
26               return  ((InitializationError) cause).getCauses();
27           if  (cause  instanceof  org.junit.internal.runners.InitializationError)
28               return  ((org.junit.internal.runners.InitializationError) cause)
29                      .getCauses();
30           return  Arrays.asList(cause);
31      }
32       private  Description describeCause(Throwable child) {
33           return  Description.createTestDescription(fTestClass,
34                   " initializationError " );
35      }
36       private   void  runCause(Throwable child, RunNotifier notifier) {
37          Description description =  describeCause(child);
38          notifier.fireTestStarted(description);
39          notifier.fireTestFailure( new  Failure(description, child));
40          notifier.fireTestFinished(description);
41      }
42  }
43  public   class  IgnoredClassRunner  extends  Runner {
44       private   final  Class <?>  fTestClass;
45       public  IgnoredClassRunner(Class <?>  testClass) {
46          fTestClass =  testClass;
47      }
48      @Override
49       public   void  run(RunNotifier notifier) {
50          notifier.fireTestIgnored(getDescription());
51      }
52      @Override
53       public  Description getDescription() {
54           return  Description.createSuiteDescription(fTestClass);
55      }
56 }

写在最后的话

写了一个周末了,这一篇终于是写完了,初次尝试将阅读框架源码以文字的形式表达出来,确实不好写,感觉自己写的太详细了,也贴了太多源码,因为有些时候感觉源码比文字更有表达能力,不过太多的源码就把很多实质的东西掩盖了,然后文章看起来也没有什么大的价值干,而且太长了,看到那么多的东西,要我自己看到也就有不想看的感觉,1万多字啊,呵呵。总之以后慢慢改进吧,这一篇就当是练习了,不想复工了,也没那么多的时间重写。。。。。。


相关文章
|
测试技术
关于Junit源码的一些探索
关于Junit源码的一些探讨
1362 0
|
设计模式 自然语言处理
|
设计模式 前端开发
|
4月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
2月前
|
Java 程序员 测试技术
Java|让 JUnit4 测试类自动注入 logger 和被测 Service
本文介绍如何通过自定义 IDEA 的 JUnit4 Test Class 模板,实现生成测试类时自动注入 logger 和被测 Service。
35 5
|
3月前
|
SQL JavaScript 前端开发
基于Java访问Hive的JUnit5测试代码实现
根据《用Java、Python来开发Hive应用》一文,建立了使用Java、来开发Hive应用的方法,产生的代码如下
82 6
|
4月前
|
测试技术
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
62 2
|
4月前
|
测试技术
如何使用 JUnit 测试方法是否存在异常
【8月更文挑战第22天】
88 0