Reference by JavaWorld
,caterpillar
測試設備(Test fixture)是測試時都會用到的一組固定物件,說是固定物件,是表示這個物件在每次測試開始時都處於一個固定的初始狀態,每個測試方法要用到這組物件時,都由這個狀態開始操作起。
在JUnit中,我們定義一個測試案例(Test case),而在測試案例中,我們可以定義setUp()與tearDown()這兩個方法來建立測試設備與拆除測試設備,我們直接使用個簡單的例子來說明何時使用這兩個方法。
首先我們定義一個簡單的類別,它可以支援物件的檔案寫入與讀出:
import java.util.*;
import java.io.*;
public class ObjectIOManager {
private String _filename;
public ObjectIOManager(String filename) {
_filename = filename;
}
public Object[] readObjects() {
ObjectInputStream objInput = null;
ArrayList students = new ArrayList();
try {
objInput = new ObjectInputStream(new FileInputStream(_filename));
System.out.print("Reading data from " + _filename + " ... ");
try {
while(true)
students.add(objInput.readObject());
}
catch(EOFException e) {}
objInput.close();
System.out.println("done");
}
catch(ClassNotFoundException e) {
e.printStackTrace();
}
catch(IOException e) {
e.printStackTrace();
}
return students.toArray();
}
public void writeObjects(Object[] objs) {
ObjectOutputStream objOutput = null;
try {
objOutput = new ObjectOutputStream(new FileOutputStream(_filename));
System.out.print("Writing data to " + _filename + " ... ");
for(int i = 0; i < objs.length; i++)
objOutput.writeObject(objs[i]);
objOutput.flush();
objOutput.close();
System.out.println("done");
}
catch(IOException e) {
e.printStackTrace();
}
}
}
我們要測試這個類別是否如我們所設計般正常工作,我們先測試可不可以寫字串物件至檔案中再讀出,然後測試一個實作Serializable的物件是否可以寫入檔案再讀出,這個實作Serializable的類別我們定義如下:
import java.io.*;
public class WritableObject implements Serializable {
private String _name; public WritableObject(String name) {
_name = name;
}
public boolean equals(Object obj) {
if(_name.equals(((WritableObject)obj)._name))
return true;
else
return false;
}
}
我們撰寫一個測試案例以進行測試:
import junit.framework.TestCase;
public class ObjectIOManagerTest extends TestCase {
public ObjectIOManagerTest(String arg0) {
super(arg0);
}
public void testSimpleObjectIO() {
ObjectIOManager manager = new ObjectIOManager("test.dat");
String[] simpleObjects = {"Test1", "Test2", "Test3", "Test4"};
manager.writeObjects(simpleObjects);
Object[] objs = manager.readObjects();
for(int i = 0; i < simpleObjects.length; i++)
assertEquals(simpleObjects[i], (String)objs[i]);
}
public void testStudentObjectIO() {
ObjectIOManager manager = new ObjectIOManager("test.dat");
String[] simpleObjects = {"Test1", "Test2", "Test3", "Test4"};
WritableObject[] writable = new WritableObject[simpleObjects.length];
for(int i = 0; i < simpleObjects.length; i++)
writable[i] = new WritableObject(simpleObjects[i]);
manager.writeObjects(writable);
Object[] objs = manager.readObjects();
for(int i = 0; i < writable.length; i++)
assertEquals(writable[i], (WritableObject)objs[i]);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(ObjectIOManagerTest.class);
}
}
在testSimpleObjectIO()與testStudentObjectIO()這兩個方法中,很顯然的有重複的程式碼,它們是可以重複使用的物件,我們將這些重複的程式碼重新撰寫有setUp()方法中,我們重新改寫如下:
import junit.framework.TestCase;
public class ObjectIOManagerTest extends TestCase {
private ObjectIOManager _manager;
String[] _simpleObjects = {"Test1", "Test2", "Test3", "Test4"};
public ObjectIOManagerTest(String arg0) {
super(arg0);
}
protected void setUp() throws Exception {
super.setUp();
_manager = new ObjectIOManager("test.dat");
}
protected void tearDown() throws Exception {
super.tearDown();
_manager = null;
}
public void testSimpleObjectIO() {
_manager.writeObjects(_simpleObjects);
Object[] objs = _manager.readObjects();
for(int i = 0; i < _simpleObjects.length; i++)
assertEquals(_simpleObjects[i], (String)objs[i]);
}
public void testStudentObjectIO() {
WritableObject[] writable = new WritableObject[_simpleObjects.length];
for(int i = 0; i < _simpleObjects.length; i++)
writable[i] = new WritableObject(_simpleObjects[i]);
_manager.writeObjects(writable);
Object[] objs = _manager.readObjects();
for(int i = 0; i < writable.length; i++)
assertEquals(writable[i], (WritableObject)objs[i]);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(ObjectIOManagerTest.class);
}
}
在上面的程式中,我們將字串陣列物件_simpleObjects設定為field,這樣可以避免每次都生成新的字串物件,而_manager則撰寫在setUp()方法中,setUp()方法會在每一次執行testXXXX()方法前呼叫,以建立一些測試裝備物件,而tearDown()會在每一次testXXXX()方法執行完畢後呼叫,以拆除測試裝備物件,在這個程式中使用tearDown()比較沒有意義,它只是將_manager參考至null物件,然後對於一些物件,例如該物件涉及到網路連線或資料庫連線時,可以在tearDown()中撰寫一些關閉連線或是關閉資源的程式,這時就顯示tearDown()的重要。
注意到setUp()與tearDown()方法在每一次testXXXX()執行的前後都會被調用,所以不要被_manager設定為field而迷惑了,在testXXXX()開始執行時,測試設備都會重新產生一組測試裝備物件,所以您的每一個testXXXX()都會得到新的測試裝備物件,這個測試裝備物件與前一次testXXXX()執行時的裝備物件是沒有關係的。