在实际的测试中,为了测试业务逻辑,必须构造一些参数或者一些资源,然后才可进行测试,最后必须释放这些系统资源。如测试数据库应用时,必须创建数据库连接Connection,然后执行操作,最后必须释放数据库的连接等。如下代码:
public void testUpdate(){
// Load the Oracle JDBC driver and Connect to the database
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
String url = "jdbc:oracle:thin:@localhost:1521:ORA91";
Connection conn = DriverManager.getConnection (url, "hr", "hr");
// Select first_name and last_name column from the employees table
ResultSet rset = stmt.executeQuery ("select FIRST_NAME, LAST_NAME from EMPLOYEES");
……………
// Disconnect
conn.close ();
}
其实这种情况很多,如测试EJB时,必须进行JNDI的LookUp,获得Home接口等。可是如果在一个TestCase中有几个测试方法,例如测试对数据库的Insert,Update,Delete,Select等操作,这些操作必须在每个方法中都首先获得数据库连接connection,然后测试业务逻辑,最后再释放连接。这样就增加了测试人员的工作,反复的书写这些代码,与JUnit当初的设计目标不一致?怎样解决这个问题?
接下来要解决的问题是给开发者一个便捷的“地方”,用于放置他们的初始化代码,测试代码,和释放资源的代码,类似对象的构造函数,业务方法,析构函数一样。并且必须保证每次运行测试代码之前,都运行初始化代码,最后运行释放资源代码,并且每一个测试的结果都不会影响到其它的测试结果。这样就达到了代码的复用,提供了测试人员的效率。
Template Method(模板方法)可以比较好得解决这个的问题。摘引其意图,“定义一个操作中算法的骨架,并将一些步骤延迟到子类中。Template Method使得子类能够不改变一个算法的结构即可重新定义该算法的某些特定步骤。”这完全恰当。这样可以使测试者能够分别来考虑如何编写初始化和释放代码,以及如何编写测试代码。不管怎样,这种执行的次序对于所有测试都将保持相同,而不管初始化代码如何编写,或测试代码如何编写。
模板方法的构成:
1、AbstractClass 定义多个抽象操作,以便让子类实现。并且实现一个具体的模板方法,它给出了一个顶级逻辑骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类里实现。模板方法也有可能调用一些具体的方法。
2、ConcreteClass 实现父类的抽象操作方法,它们是模板方法的组成步骤。每一个AbstractClass可能有多个ConcreteClass与之对应,而每一个ConcreteClass分别实现抽象操作,从而使得顶级逻辑的实现各不相同。
模板方法模式代码的实现:
Template:
public abstract class Template {
public void template() {
this.method1();
this.method2();
this.method3();
}
public abstract void method1();
public abstract void method2();
public abstract void method3();
}
ConcreteTemplate:
public class ConcreteTemplate extends Template{
@Override
public void method1() {
System.out.println("method1");
}
@Override
public void method2() {
System.out.println("method2");
}
@Override
public void method3() {
System.out.println("method3");
}
}
Client:
public class Client {
public static void main(String[] args) {
Template templateTest = new ConcreteTemplate();
templateTest.template();
}
}
在junit3.8的源码中:
于是我们首先把 TestCase分成几个方法,哪些是抽象操作以便让开发人员去实现,哪个是Junit 中的设计模式,具体的模板方法,现在我们来看TestCase源码:
public abstract class TestCase extends Assert implements Test {
//定义抽象操作,以便让子类实现
protected void setUp() throws Exception {
}
protected void runTest() throws Throwable {
}
protected void tearDown() throws Exception {
}
//具体的模板方法,定义出逻辑骨架
public void runBare() throws Throwable {
setUp();
runTest();
tearDown();
}
}
setUp方法让测试人员实现,去初始化测试信息,如数据库的连接, EJB Home接口的JNDI等,而tearDown方法则是实现测试完成后的资源释放等清除操作。runTest方法则是开发人员实现的测试业务逻辑。最后TestCase的方法runBare则是模板方法,它实现了测试的逻辑骨架,而测试逻辑的组成步骤setUp, runTest, teardown,推迟到具体的子类实现,如一个具体的测试类:
public class TestHelloWorldTestClientJUnit1 extends TestCase {
public void setUp() throws Exception {
initialize();//初始化JNDI信息
create(); //获得EJB的Home接口,和远程接口
}
public void testGetMessage() throws RemoteException {
assertNotNull(ERROR_NULL_REMOTE, helloWorld);
this.assertEquals("Hello World",helloWorld.getMessage(""));//测试业务逻辑
}
public void tearDown() throws Exception {
helloWorldHome = null; //释放EJB的Home接口
helloWorld = null; //释放EJB的远程接口
}
}
子类实现了setUp,tearDown方法,和一个测试方法testGetMessage,为什么名称不是runTest,这就是在下面介绍Adapter模式的原因,它把testGetMessage方法适配成runTest。
效果
我们来考虑经过使用Template Method模式后给系统的架构带来了那些效果:
1、 在各个测试用例中的公共的行为(初始化信息和释放资源等)被提取出来,可以避免代码的重复,简化了测试人员的工作。
2、在TestCase中实现一个算法的不变部分,并且将可变的行为留给子类来实现。增强了系统的灵活性。使JUnit框架仅负责算法的轮廓和骨架,而测试的开发人员则负责给出这个算法的各个逻辑步骤。