Junit源码分析(转)

Junit源码分析

junit是我们平时开发中天天用到的测试框架,为了了解器内部隐藏的机关,特意分析了一下源码,这里我们用的是Junit3.8版本。

1.包的划分

Junit源码分析(转)

junit.awtui,这个是junit的awt实现的ui界面组件

junit.extensions这个是junit核心功能之外的扩展点,对TestCase的装饰,Demorator模式的很好的例子

junit.framework,这个是junit的核心功能,像我们平时常用的TestCase,TestSuit类都是在这的,还有Assert类,提供了我们测试中常用的断言静态方法

junit.runner,这个包下是运行TestCase的核心类,最重要的是TestListener和BaseTestRunner类,这个后面会解释到

junit.swingui,这个包的作用和junit.awtui是完全一直的,只不过是用Swing的方式实现而已

junit.textui,功能等价与junit.awtui和junit.swingui,junit控制台输出的一个实现,下面的源码解读中,我们以textui包作为ui输出解释

从上面的分析可以看出,Junit运行的最小单元是framework+runner+ui-简洁就是美

2.框架的核心类

junit的源码非常小,但是设计的非常巧妙,核心类/接口大约有5-6个的样子,通过分析,我们可以得出一下的核心类图

Junit源码分析(转)

Test接口:这个是最顶层的测试实体抽象,接口中只定义了两个方法,countTestCases()表示当前测试实体有多少个测试用例,另一个就是核心的run方法了

TestCase类–Test接口的一个实现类,也是我们平时常用的测试父类,表示一个要进行测试的对象,在实际代码中,代表我们一个个的测试方法(junit3.8中只那些继承了TestCase类中那些以test开头的测试方法)

TestSuit类–Test接口的另一个实现,见名知其意,是一组测试用例类,和TestCase是一对多关系

TestResult类-这个是测试结果的包裹类,提供了常用的方法,如测试失败时,测试成功时做一些处理,基于Observer模式实现

TestListener接口–整个junit核心基于ObserVer模式实现,这个接口则是对Observer的抽象

BaseTestRunner类–TestListener的抽象实现,定义了通用的公共方法,而TestRunner(swing,atw,textui)则是扩展了基类的功能,提供了ui的展示层功能

2.源码解读

上面介绍了框架的核心类,接下来看下我们的核心类之间如何协调来完成测试的

Junit源码分析(转)

3.junit的设计思想

3.1观察者模式

观察者模式的核心对象有两个,一个是要监控的对象,junit里就是一个个测试用例TestCase,另一个是观察者,当监控对象发生变化时(测试用例开始前,出错,运行失败,运行结束事件),做出一些操作(GUI更新)。从第一节的类层次中可以看出,junit本身对TestListener做了封装,上层可以通过继承这个类来快速的开发出另一种观察者的实现,如我们在Eclipse中运用junit插件,其实就是插件中扩展一下BaseTestRunner的功能,增加eclipse展示的ui而已,而底层的核心逻辑是不需要变化的

3.2模板方法模式

可以这样理解模板方法模式,在父类中定义一个算法的骨架,而具体的实现类则放在子类中去实现,junit很多地方都充斥着这种思想

TestResult.run方法就是一个模板方法startTest,runTest,endTest,依次执行,我们熟知的TestCase中,每个测试方法执行前都会运行setUp,测试方法运行完后运行tearDown方法,这个也是个模板方法模式的实现,在TestCase中有个我们不直接调的核心模板方法

/**
* Runs a TestCase.
*/
protected void run(final TestCase test) {
startTest(test);
Protectable p= new Protectable() {
public void protect() throws Throwable {
test.runBare();
}
};
runProtected(test, p);
endTest(test);
}
这下子搞明白了,这个方法则是在更顶层的TestResult.run中调用runBare(),然后runBare()中调用我们熟知的那些setUp,tearDown
3.3约定大于配置
测试junit3测试用例的同学都知道,要写一个让junit可以运行的测试用例,我们的测试方法必须是public void test***()这种格式的,否则junit不会执行,为什么呢?看下junit的实现细节
private void addTestMethod(Method m, Vector names, Class theClass) {
String name= m.getName();
if (names.contains(name))
return;
if (! isPublicTestMethod(m)) {
if (isTestMethod(m))
addTest(warning(“Test method isn’t public: “+m.getName()));
return;
}
names.addElement(name);
addTest(createTest(theClass, name));
}
private boolean isTestMethod(Method m) {
String name= m.getName();
Class[] parameters= m.getParameterTypes();
Class returnType= m.getReturnType();
return parameters.length == 0 && name.startsWith(“test”) && returnType.equals(Void.TYPE);//这个地方判断方法以test开头是写死的:)
}
这种做法有两种争论,一种是支持派,有这样一个约定,大家都没的选,样式统一规范,看一眼就知道是个测试方法,这种方式和maven项目的默认项目结构有点类似–要到这个山头混,就得听山大王的:)
另一种是反对派,认为这种方式太过死板,为什么测试方法不可以有参数,为什么不能不以test开头,这可能也是后来testgn,junit4横空出世的原因吧:)
4.收获&总结
一个优秀的框架不在于它有多大,内部用了多先进的技术,用最简单的东西实现最常用的功能就可以了,研究源码主要收获如下
1).系统设计(特别是框架设计)一定要简洁易懂,简洁就是美
2).为系统设计留有余地–系统的内核在于定义核心逻辑和算法,但是具体实现的多样化可以留给后来人自由扩展–对接口和抽象编程,定义钩子方法

相关推荐