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主要执行流程及相关代码分析
基本代码流程

Junit 的全生命周期(来源:Junit in Action)
用于分析的测试例子
这里不在体介绍Junit基本使用,直接从源码角度进行分析,通过一个最一般的案例来讨论一些Junit的设计思路,源码采用Junit3.8.1版本。
下面这个是一个简单的例子,主要是一个Caculator的类,里面有4个方法,并且实现了对应的测试类,之后主要分析这个Caculator类所对应的Junit测试类的源代码的实现。
Caculator测试类:



主要运行模块

加载测试类上面是Junit框架大致的运行模块,下面结合实际的代码对每个模块进行说明:
加载测试类的时候可以通过编辑器自动加载,即直接通过Run AS —> Junit Test来进行,但是这样是编译器内部自动加载运行的,不便于分析,这里通过显示的调用junit.textui.TestRunner.run(TestCaculator.class); 来加载类,也可以通过调用swingui来进行交互。为了便于分析,这里使用texui。

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

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

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


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

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

这一部分的主要功能在于生成一个TestRunner实例并得到其中的相关运行内容,生成TestRunner实例之后,要注册对应的监听器,这里textui使用的是fPrinter(这里用到了观察者模式),之后是记录当前的时间,运行测试代码suite.run,记录运行之后的时间,得到运行的时间,之后回调监听器的方法,通过textui监听器打印出对应的信息。
suite.run是比较重要的部分(这里用到了组合模式)
如果当前的suite是一个TestSuite类型,则遍历其所维护的fTests向量,对其中的每一个TestSuite或者TestCase继续调用其run方法。



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

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

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

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

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

之后测试方法执行结束后,再通过循环的方式通知所有的监听器,来执行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方法,有点类似于树的遍历,很好的体现出了组合模式的思路,对于问题的解耦比较有帮助,是简单元素与复杂元素都有同样的处理方法。


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

回调就是一种双向调用模式,什么意思呢,就是说,被调用方在被调用时也会调用对方
观察者模相当于是回调模式的扩充。在观察者模式中,观察者相当于上面例子中的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的实现顺序:

模版方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(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://www.cnblogs.com/zhuxiongfeng/archive/2010/04/09/1708615.html
圣思园网络公开课
Junit 3.8源码分析的更多相关文章
- JUnit源码分析 - 扩展 - 自定义Rule
JUnit Rule简述 Rule是JUnit 4.7之后新加入的特性,有点类似于拦截器,可以在测试类或测试方法执行前后添加额外的处理,本质上是对@BeforeClass, @AfterClass, ...
- JUnit源码分析 - 扩展 - 自定义RunListener
RunListener简述 JUnit4中的RunListener类用来监听测试执行的各个阶段,由RunNotifier通知测试去运行.RunListener与RunNotifier之间的协作应用的是 ...
- Junit 3.8.1 源码分析之两个接口
1. Junit源码文件说明 runner framework:整体框架; extensions:可以对程序进行扩展; textui:JUnit运行时的入口程序以及程序结果的呈现方式; awtui:J ...
- Junit 3.8.1 源码分析(一)
写在前面:本文基于Junit3.8.1版本,因为这是我第一次进行源码学习,先从简单的源码开始学起 1. 示例代码 1.1 准备工作 下载Junit3.8.1的JAR包 需要下载junit-3.8.1- ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
- 【JUnit4.10源码分析】5 Statement
假设要评选JUnit中最最重要的类型.或者说核心,无疑是org.junit.runners.model.Statement.Runner等类型看起来热闹而已. package org.junit.ru ...
- Robotium源码分析之Instrumentation进阶-attach
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架.鉴于之 ...
- Robotium源码分析之Instrumentation进阶
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架.鉴于之 ...
- Hadoop之HDFS原理及文件上传下载源码分析(上)
HDFS原理 首先说明下,hadoop的各种搭建方式不再介绍,相信各位玩hadoop的同学随便都能搭出来. 楼主的环境: 操作系统:Ubuntu 15.10 hadoop版本:2.7.3 HA:否(随 ...
随机推荐
- vb中typename函数
适用于获得一个变量的类型名称的, 比如 A 是一个字符串变量,那么 TypeName(A)=String
- De Moivre–Laplace theorem
网址:https://en.wikipedia.org/wiki/De_Moivre%E2%80%93Laplace_theorem De Moivre–Laplace 中心极限定理的证明.主要用到s ...
- bzoj4883 [Lydsy1705月赛]棋盘上的守卫 最小生成基环树森林
题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=4883 题解 每一行和每一列都必须要被覆盖. 考虑对于每一行和每一列都建立一个点,一行和一列之间 ...
- Java 学习输入1234 求和
输入任何整数都能求和 import java.util.Scanner; /** * @author 作者anil E-mail:888@yirose.com * @date 创建时间:2017年3月 ...
- Python---进阶---文件操作---搜索文件和保存搜索结果
### 编写一个程序,用户输入文件名以及开始搜索的路径,搜索该文件是否存在,如果遇到文件夹,则进入该文件夹继续搜索 - input 去接受用户输入的文件名和开始搜索的路径 - os.path.isdi ...
- nginx限制文件访问速率
需求: 一个文件下载功能需要限制文件同时访问的并发数和当个连接的访问速率: 配置: 在http context内增加: limit_conn_zone $binary_remote_addr zone ...
- Eclipse 中的 parameter参数,property属性,preference首选项 区别
parameter参数 1.配置框架 web.xml <init-param> <param-name>contextConfigLocation</param-name ...
- go语言实战 摘抄
append 函数append会智能地处理底层数组的容量增长.在切片的容量小于1000个元素时,总是会成倍地增加容量.一旦元素个数超过1000,容量的增长因子就会设为1.25, 也就是每次增加25%的 ...
- 【bzoj4137】[FJOI2015]火星商店问题
*题目描述: 火星上的一条商业街里按照商店的编号1,2 ,…,n ,依次排列着n个商店.商店里出售的琳琅满目的商品中,每种商品都用一个非负整数val来标价.每个商店每天都有可能进一些新商品,其标价可能 ...
- Qt android 配置
http://www.cnblogs.com/ztzheng/p/3703716.html