Junit 3.8源码分析

JUnit背景介绍

JUnit是由Erich Gamma和Kent Beck 编写的一个回归测试框架(regression testing framework)。Junit测试是程序员测试,即所谓的白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。

Junit引入了极限编程的理念,这样可以强制你在写代码之前好好的思考代码(方法)的功能和逻辑,否则编写的代码很不稳定,那么你需要同时维护测试代码和实际代码,这个工作量就会大大增加。

Junit通常的版本有3.x与4.x两种,在3.x版本中需要通过继承TestCase的方式来进行测试,并且测试方法的名称也需要有固定的要求,在4.x版本中进行了较多的改变,引入了注解的方式,书写变得更灵活。

Junit的发明引入了一些新的变编程理念:

单元测试并不能证明你的代码是正确的,只能证明你的代码是没有错误的。

Keep bar green and keep your code cool.

Junit3.8主要执行流程及相关代码分析

基本代码流程

clip_image002

Junit 的全生命周期(来源:Junit in Action)

用于分析的测试例子

这里不在体介绍Junit基本使用,直接从源码角度进行分析,通过一个最一般的案例来讨论一些Junit的设计思路,源码采用Junit3.8.1版本。

下面这个是一个简单的例子,主要是一个Caculator的类,里面有4个方法,并且实现了对应的测试类,之后主要分析这个Caculator类所对应的Junit测试类的源代码的实现。

Caculator测试类:

clip_image003

clip_image004

clip_image005

主要运行模块

clip_image001


加载测试类
上面是Junit框架大致的运行模块,下面结合实际的代码对每个模块进行说明:

加载测试类的时候可以通过编辑器自动加载,即直接通过Run AS —> Junit Test来进行,但是这样是编译器内部自动加载运行的,不便于分析,这里通过显示的调用junit.textui.TestRunner.run(TestCaculator.class); 来加载类,也可以通过调用swingui来进行交互。为了便于分析,这里使用texui。

clip_image010

这里主要是实现了两部分功能,一个是根据传入的一个testclass的Class实例来生成一个新的TestSuite实例,之后是将新生成的实例传入run方法中。

clip_image011

之后在run方法中,会生成一个TestRunner实例,并且运行这个实例的doRun方法

生成TestSuite实例

clip_image013

TestSuite实例的生成过程的代码如上,主要是通过反射的方法得到TestClass(这里是TestCaculator类)的构造方法类,之后检查类型是否为public的,之后生成一个Methods的数组,通过getDeclaredMethods()方法通过反射得到TestCaculator的全部Methods实例。之后对Methods数组进行遍历,对每一个方法进行检查,如果检查通过就将这个Methods实例添加到names向量中。

具体进行检查的代码如下:

clip_image014

clip_image016

具体就是检查methods数组中的每个Method实例是否包含test关键字,是否为public类型的,以及是否出现重复。要是通过的话就创建一个TestCase (这里仅仅对本例子)实例,并存入TestSuite所维护的fTests向量中。实际上这里的createTest是根据多态来进行的,可能生成TestCase、TestSuite、以及TestDecorator的实例,它们都实现了Test接口。

clip_image018

TestRunner实例运行doRun及测试方法的执行

clip_image020

这一部分的主要功能在于生成一个TestRunner实例并得到其中的相关运行内容,生成TestRunner实例之后,要注册对应的监听器,这里textui使用的是fPrinter(这里用到了观察者模式),之后是记录当前的时间,运行测试代码suite.run,记录运行之后的时间,得到运行的时间,之后回调监听器的方法,通过textui监听器打印出对应的信息。

suite.run是比较重要的部分(这里用到了组合模式)

如果当前的suite是一个TestSuite类型,则遍历其所维护的fTests向量,对其中的每一个TestSuite或者TestCase继续调用其run方法。

clip_image022

clip_image024

clip_image026

如果当前的suite是一个TestCase的类型则通过匿名内部类的方式运行实际编写的测试代码:

clip_image027

上面部分的runProtected函数就是执行protect方法并且对抛出的异常进行处理。

clip_image029

这里的startTest就是表示在测试代码开始运行之前,通知对应的监听器进行一些处理,不同的ui的监听器有不同的处理方式,这里通过循环遍历全部的监听器。

clip_image031

之后的runBare方法用到了模板方法模式,这里表示按照顺序运行setUp方法,runTest方法(实际的测试代码),以及tearDown方法。

clip_image033

RunTest方法也是通过反射的机制来运行,通过动态加载类并得到Method方法实例,最后调用Method方法实例的invoke方法动态执行实际所编写的测试代码

clip_image035

之后测试方法执行结束后,再通过循环的方式通知所有的监听器,来执行endTest方法,不同的ui会通过不同的方式显示出测试方法所执行的结果,显示是否通过或者是打印错误信息。

其他核心类

TestCase + TestSuite + BaseTestRunner = TestResult

以上三个类是JUnit主要核心类,共同产生测试结果。

TestCase(测试用例)扩展了JUnit的TestCase类的类。它以testXXX方法是形式包含一个或多个测试。

TestSuite(测试集合)一组测试(多个TestCase组合在一起测试)。

TestRunner(测试运行器)实际上指的是任何继承BaseTestRunner的TestRunner的类。也就是说BaseTestRunner是所有TestRunner的超类。

还有四个类紧密配合在一起来完成整个测试。分别是:

Assert – 当条件成立时assert方法保持沉默,当条件不成立时就抛出异常。

TestResult – 包含了在测试中发生的所有错误或者失败。

Test – 可以运行Test并把结果传递给TestResult。

TestListener – 测试中产生的所有事件(开始、结束、错误、失败)会通知TestListener。

相关设计模式

组合模式

Junit中对于TestCase与TestSuite的设计很好的体现了组合模式,TestSuite就像是根节点,TestClass就像是叶结点,他们都实现了Test接口,不同的类型有不同的run方法,有点类似于树的遍历,很好的体现出了组合模式的思路,对于问题的解耦比较有帮助,是简单元素与复杂元素都有同样的处理方法。

clip_image036

clip_image027[1]

观察者模式

在Junit3.8中的监听器的使用就是一种典型的观察者模式,TessResult会在相关的操作执行完成之后回调Listerner的方法,同时Listener来进行对应的操作,TestResult中的fListestiners维护了一组监听器实例,每当固定阶段的任务执行完成时候就会告诉所有的监听器执行对应的操作,比如执行完测试部分输出信息:

clip_image037

回调就是一种双向调用模式,什么意思呢,就是说,被调用方在被调用时也会调用对方

观察者模相当于是回调模式的扩充。在观察者模式中,观察者相当于上面例子中的A,被观察者相当于上面例子中的B。不同的是有多个观察者,一个被观察者,也可以是多个。

观察者模式的官方定义:

观察者模式定义了对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

当一个被观察者的状态发生改变所有依赖它的对象都发生变化。

该模式的几个角色:

Subject(被观察的对象接口 ),规定ConcreteSubject的统一接口。

每个Subject可以有多个Observer,(通过在具体实现类中添加观察者引用的ArrayList或者其他类似方式来实现,这是实现回调方式的关键),观察者是先于被观察者创建的,并且要将对于观察者的引用加入到被观察者的ArrayList中,这样才能在被观察者的信息改变了之后通知对应的观察者。(因为观察者是先于被观察者而创建的 而最后要通过被观察者来调用观察者,进行信息的更新,即后创建的,反过来调用先创建的对象,所以是回调)

ConcreteSubject(具体被观察对象)

维护对所有具体观察者的引用的列表,状态发生变化时会发送通知给所有注册的观察者。(通过列表中对于obversor的引用,来是实现回调,更新观察者)

从功能上来讲,ConcreteSubjec至少要包含 addobservor(添加一个观察者) deletobservor(删除一个观察者)以及updateobservor三个部分,通过updateobservoer来进行回调,对观察者进行更新,或者是通知观察者来执行相对应的事务。

Observer(观察者接口)

规定ConcreteObserver的统一接口;

定义了一个update()方法,在被观察对象状态改变时会被调用。

ConcreteObserver(具体观察者)

维护一个对ConcreteSubject的引用,(这个可以通过对于ConcreteSubject进行引用,以此来调用ConcreteSubject中的方法,比如添加删除之类的操作,这样的话,不用在main函数中生成ConcreteSubject的具体的类了,每一个observer都含有add delete的方法)

特定状态与ConcreteSubject同步,实现Observer接口,通过update()方法接收ConcreteSubject的通知。(回调过来的具体执行部分)

模板方法模式

Junit中执行测试代码的部分是典型的模板方法模式,通过指定相关方法的执行顺序,提升出不变的部分,抽象出了实际算法的骨架,保证了setup与teardown的实现顺序:

clip_image038

模版方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本法方法总汇起来的方法叫做模版方法(template method),这个设计模式的名字就是从此而来。

Junit3.8代码质量分析(ppt中完善)

使用Vector效率较低

用于Junit 3.8版本时间比较老,在集合框架中的相关部分大量使用了Vecotor,最好可以换成ArrayList,Vector在实际使用中效率也不如ArrayList。

参考资料

http://baike.baidu.com/view/66926.htm?fr=aladdin

http://wenku.baidu.com/link?url=fxeal7aHRTLq9no6JZhnRCifyUQQsrABSTdqCfirGWqXaGFJsDEAi831bQNIIba1EzvhSP4zxvILhI3v46x-Wt7oxqWEvAnqLWzMEQVYlmm

http://www.cnblogs.com/Goden/

http://www.cnblogs.com/zhuxiongfeng/archive/2010/04/09/1708615.html

圣思园网络公开课

原文地址:https://www.cnblogs.com/Goden/p/4165846.html