JUnit 中一笔优雅的设计

...
setUp();
testXXX();
tearDown();
用过JUnit的对这些应该是烂熟于胸了,这三部曲简述了JUnit的最基本的操作过程,JUnit的设计者为了保证这三部操作能像原子操作般执行,使用了一个很通用的设计模式,Template pattern, 典型的模板模式基本上是按照下面的方式来设计的:

/**
*定义一个抽象的类,并定义几个要步骤化的抽象方法,
×把这些操作交付给起子孙后代去实现,
*再定义一个总控这个步骤的方法--模板方法,这里对manipulate做了final修饰,
×不让其子类改写,当然如果开明点,也可以把这个final给去掉,这样其子孙也
×也有权利设计这个操作内容了。
*/
public abstract class A{
protected abstract void operOne();
protected abstract void operTwo();
protected abstract void operThree();
public final void manipulate(){
operOne();
operTwo();
operThree();
}
}

/**
*继承A,并实现自己的操作
*/
public class SubA{

protected void operOne(){
System.out.pritln("step one");
}
protected void operTwo(){
System.out.pritln("step two");
}
protected void operThree(){
System.out.pritln("step three");
}
}

JUnit是这样做的吗?肯定不是了。那是怎样处理的呢?
让我们来赏析一下JUnit的内核源码片段:
package junit.framework;

import java.util.Vector;
import java.util.Enumeration;

/**
* A <code>TestResult</code> collects the results of executing
* a test case. It is an instance of the Collecting Parameter pattern.
* The test framework distinguishes between <i>failures</i> and <i>errors</i>.
* A failure is anticipated and checked for with assertions. Errors are
* unanticipated problems like an <code>ArrayIndexOutOfBoundsException</code>.
*
* @see Test
*/
public class TestResult extends Object {
...
/**
* Runs a TestCase.
*/
public void runProtected(final Test test, Protectable p) {
try {
p.protect();
}
catch (AssertionFailedError e) {
addFailure(test, e);
}
catch (ThreadDeath e) { // don't catch ThreadDeath by accident
throw e;
}
catch (Throwable e) {
addError(test, e);
}
}
/**
* 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摘录的一个片段,这里要强调的是接口Protectable,这个接口就是用来保护setUp(),runBase(),tearDown()操作的,也就是说在执行某个测试的时候系统一定要经过这些步骤,当然JUnit的作者是在哪些地方去实现了Protectabe接口的呢?笔者去跟踪了源码,实现接口的位置有两处,如下:
public abstract class TestCase extends Assert implements Test {
...
/**
* Runs the bare test sequence.
* @exception Throwable if any exception is thrown
*/
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
public TestResult run() {
TestResult result= createResult();
run(result);
return result;
}
...
}

另外一处:
package junit.extensions;

import junit.framework.*;

/**
* A Decorator to set up and tear down additional fixture state.
* Subclass TestSetup and insert it into your tests when you want
* to set up additional state once before the tests are run.
*/
public class TestSetup extends TestDecorator {

public TestSetup(Test test) {
super(test);
}
public void run(final TestResult result) {
Protectable p= new Protectable() {
public void protect() throws Exception {
setUp();
basicRun(result);
tearDown();
}
};
result.runProtected(this, p);
}
/**
* Sets up the fixture. Override to set up additional fixture
* state.
*/
protected void setUp() throws Exception {
}
/**
* Tears down the fixture. Override to tear down the additional
* fixture state.
*/
protected void tearDown() throws Exception {
}
}

到这想必大家已经基本上理解了设计者的意图了,但相比较于继承父类的方式来实现模板化,使用接口更加体现了系统的灵活性,假设前面提到的类A是由第三方提供的component,但这个component却没有提供类似的模板方面,这个时候我们可以考虑使用模板化接口来解决这个问题,但是作者可不是直接实现接口,而是采用方法中调用Protectable的方式来事处理了,这更加体现了系统的灵活度(当然这也是Inversion Of Contorl理念之一)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值