Posted on 2007-04-06 09:54
dennis 阅读(2653)
评论(0) 编辑 收藏 所属分类:
模式与架构 、
源码解读
在JUnit执行测试时,我们经常需要初始化一些环境供测试代码使用,比如数据库连接、mock对象等等,这些初始化代码应当在每一个测试之前执行并在测试方法运行后清理。在JUnit里面就是相应的setUp和tearDown方法。如果没有这两个方法,那么我们要在每个测试方法的代码内写上一大堆重复的初始化和清理代码,这是多么愚蠢的做法。那么JUnit是怎么让setUp和tearDown在测试执行前后被调用的呢?
如果你查看下TestCase方法,你会发现TestCase和TestSuite的run()方法都是将执行测试的任务委托给了TestResult,由TestResult去执行测试代码并收集测试过程中的信息(这里用到了Collecting Parameter模式)。
public TestResult run() {
TestResult result= createResult();
run(result);
return result;
}
/**
* Runs the test case and collects the results in TestResult.
* This is the template method that defines the control flow
* for running a test case.
*/
public void run(TestResult result) {
result.run(this);
}
我们直接找到TestResult,看看它的run方法:
/**
* 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);
}
这里实例化了一个内部类,内部类实现了
Protectable接口的 protect()方法,并执行传入的TestCase的runBare()方法,显然,真正的测试代码在TestCase的runBare()方法中,让我们来看下:
//将被子类实现
protected void setUp() throws Throwable {
}
//同上,将被具体的TestCase实现
protected void tearDown() throws Throwable {
}
/**
* 模板方法
* Runs the bare test sequence.
* @exception Throwable if any exception is thrown
*/
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
真相水落石出,对于每一个测试方法,都遵循这样的模板:setUp->执行测试 runTest()->tearDown。这正是模板方式模式的一个应用例子。什么是template method模式呢?
Template Method模式
类行为模式的一种
1.意图:定义一个操作中的算法的骨架,而将一些延迟步骤到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
2.适用场景:
1)一次性实现算法的不变部分(基本骨架),将可变的行为留给子类来完成
2)子类中的公共部分(比如JUnit中的初始化和清理)被抽取到一个公共父类中以避免代码重复。
3)控制了子类的扩展,这里其实也有类似回调函数的性质,具体步骤先在骨架中注册,在具体执行时被回调。
3.UML图和结构
抽象父类定义了算法的基本骨架(模板方法),而不同的子类实现具体的算法步骤,客户端由此可以与算法的更改隔离。
4.效果:
1)模板方法是代码复用的基本技术,在类库中经常使用,可以减少大量的代码重复
2)通过隔离算法的不变和可变部分,增加了系统的灵活性,扩展算法的某些步骤将变的很容易。
了解了Template Method模式之后,让我们回到JUnit的源码,看看runTest()方法,这里主要应用的是java的反射技术,对于学习反射技术的有参考价值:
protected void runTest() throws Throwable {
Method runMethod= null;
try {
runMethod= getClass().getDeclaredMethod(fName, new Class[0]);
} catch (NoSuchMethodException e) {
fail("Method \""+fName+"\" not found");
}
if (runMethod != null && !Modifier.isPublic(runMethod.getModifiers())) {
fail("Method \""+fName+"\" should be public");
}
try {
runMethod.invoke(this, new Class[0]);
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throw e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}