博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入JUnit源码之Runner
阅读量:6328 次
发布时间:2019-06-22

本文共 43902 字,大约阅读时间需要 146 分钟。

初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑。不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉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万多字啊,呵呵。总之以后慢慢改进吧,这一篇就当是练习了,不想复工了,也没那么多的时间重写。。。。。。

转载地址:http://sdzoa.baihongyu.com/

你可能感兴趣的文章
Scala学习(三)----数组相关操作
查看>>
Matlab基于学习------------------函数微分学
查看>>
Dundas 系列
查看>>
Windows的命令行查看,修改,删除,添加环境变量
查看>>
iOS 图文混排
查看>>
64. Minimum Path Sum
查看>>
Windows Live Writer 使用指南
查看>>
分析iOS Crash文件,使用命令符号化iOS Crash文件
查看>>
R学习笔记 第五篇:字符串操作
查看>>
在Mac OS下配置PHP开发环境
查看>>
(转)介绍下Nuget在传统Asp.net项目中的使用
查看>>
C# ArcEngine 实现点击要素高亮并弹出其属性
查看>>
初识GO语言——安装Go语言
查看>>
SDK命令行操作
查看>>
基于Bootstrap的DropDownList的JQuery组件的完善版
查看>>
EXTJS学习系列提高篇:第二十四篇(转载)作者殷良胜,ext2.2打造全新功能grid系列--阅增删改篇...
查看>>
Hadoop MapReduce编程 API入门系列之分区和合并(十四)
查看>>
判断二叉树是否平衡、是否完全二叉树、是否二叉排序树
查看>>
并查集的应用之求解无向图中的连接分量个数
查看>>
7个神奇的jQuery 3D插件
查看>>