在 使用 JUnit 3.x 中介紹的,幾乎就是使用TestCase最常見的方式了,這邊要 來深入討論一下TestCase。
在JUnit 3.x中撰寫測試案例,必須繼承TestCase類別,TestCase類別繼承自Assert類別,最主要的目的,是方便使用Assert類別中定義的諸多靜態 方法,諸如assertXXX()、fail()等斷言方法。
assertXXX()方法無需太多解釋,XXX名稱各指出斷言某個情況。fail()方法值得略為一題,呼叫了fail()方法必然丟出錯誤 (Error),因為其於Assert中的定義為:
static public void fail(String message) {
throw new AssertionFailedError(message);
}
static public void fail() {
fail(null);
}
fail()使用的時機之一,就是在撰寫測試案例時,於每個testXXX()中先失敗作為開始。例如:
public void testABC() {
fail("單元測試尚未撰寫");
}
fail("單元測試尚未撰寫");
}
在IDE自動程 式碼產生測試案例時,通常都會自動填上fail(),以提醒開發人員尚有單元測試未撰寫。fail()使用的另一個例子,就是測試例外是否發生。例如:
public void
testSomeException() {
try {
some.doSome("somValue"); // 應該丟出例外
fail("沒有如預期丟出例外");
}
catch(SomeException ex) {
// 什麼都不作
}
}
try {
some.doSome("somValue"); // 應該丟出例外
fail("沒有如預期丟出例外");
}
catch(SomeException ex) {
// 什麼都不作
}
}
上例中,預期doSome()方法會丟出例外,如果實際上測試沒有丟出例外,那麼就會執行到fail()方法,表示測試失敗,如果如預期丟出例外,則會被 捕捉(catch),此時什麼都不作,表示測試成功。。
TestCase除了繼承Assert類別外,還實作了Test介面:
public abstract class TestCase extends
Assert implements Test {
...
}
...
}
正如之前的範例探討,TestCase實作Test介面,是為了以 Command 模式 實現測試之執行,在JUnit 3.x的設計中,最後會以反射 方式執行測試方法(無論是預設的testXXX()或是自行指定的測試方法),而在執行測試方法前,會運行setUp()與tearDown():
protected void setUp() throws Exception {
}
protected void tearDown() throws Exception {
}
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
protected void runTest() throws Throwable {
...以反射運行測試方法
}
這也就是為何可以定義setUp()與tearDown(),於每次testXXX()(或指定的測試方法)前後執行 之。
在執行測試時,你會發現到有Failure與Error兩種測試尚未通過的訊息。
Failure指的是預期結果與實際運行結果不同,例如 當你使用assertEquals()或其它assertXXX()方法斷言失敗時,就會回報Failure,這時候你要檢查你的單元方法中的邏輯設 計是否有誤。
Error指的是程式在斷言方法執行之前,程式就因 為某種錯誤引發例外而終止,例如在測試方法中因丟出某個例外,使得測試方法無法正確執行至斷言就提前結束,這時你要檢查測試方法中是否有未考慮到的情況而 引發流程突然中斷。
如果你檢視Test介面的定義,會發現它定義的run()方法有傳入TestResult:
public abstract void run(TestResult result);
TestResult會收集測試過程中所有單元測試的結果,Failure與Error各收集在一個Vector中:
public synchronized void addError(Test test, Throwable t) {
fErrors.addElement(new TestFailure(test, t));
for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {
((TestListener)e.nextElement()).addError(test, t);
}
}
public synchronized void addFailure(Test test, AssertionFailedError t) {
fFailures.addElement(new TestFailure(test, t));
for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {
((TestListener)e.nextElement()).addFailure(test, t);
}
}
如果你使用JUnit的TestRunner,則 TestRunner會為你建立TestResult。TestCase提供了一個公開的run()方法,會建立預設的TestResult來運行測試:
protected TestResult createResult() {
return new TestResult();
}
public TestResult run() {
TestResult result= createResult();
run(result);
return result;
}
public void run(TestResult result) {
result.run(this);
}
簡單地說,如果你不想透過TestRunner,則可以簡單的呼叫run()方法,而後取得TestResult來查看測試結果。例如:
- CalculatorTest.java
package cc.openhome;
import junit.framework.TestCase;
import junit.framework.TestResult;
public class CalculatorTest extends TestCase {
private Calculator calculator;
public CalculatorTest() {}
public CalculatorTest(String name) {
super(name);
}
@Override
protected void setUp() {
calculator = new Calculator();
}
@Override
protected void tearDown() {
calculator = null;
}
public void testPlus() {
int expected = 5;
int result = calculator.plus(3, 2);
assertEquals(expected, result);
}
public void testMinus() {
int expected = 1;
int result = calculator.minus(3, 2);
assertEquals(expected, result);
}
public static void main(String[] args) {
CalculatorTest[] calculatorTests = {
new CalculatorTest("testPlus"),
new CalculatorTest("testMinus")};
for(CalculatorTest test: calculatorTests) {
TestResult result = test.run();
System.out.println(test.getName());
System.out.println("\tError: " + result.errorCount());
System.out.println("\tFailure: " + result.failureCount());
}
}
}
如果你對於Failure與Error、測試開始與結束有興趣,並想作一些處 理,則可以實作TestListener:
public interface TestListener {
public void addError(Test test, Throwable t);
public void addFailure(Test test, AssertionFailedError t);
public void endTest(Test test);
public void startTest(Test test);
}
public void addError(Test test, Throwable t);
public void addFailure(Test test, AssertionFailedError t);
public void endTest(Test test);
public void startTest(Test test);
}
之後透過TestResult的addListener()加入實作 的傾聽器,則在Failure與 Error、測試開始與結束, TestResult都會通知你的傾聽器,讓你作適當的處理。例如:
...
CalculatorTest[] calculatorTests = {
new CalculatorTest("testPlus"),
new CalculatorTest("testMinus")};
TestResult result = new TestResult();
result.addListener(new TestListener() {
// 方法的實作
});
for(CalculatorTest test: calculatorTests) {
test.run(result);
}
for(Enumeration e = result.failures(); e.hasMoreElements();) {
TestFailure failure = (TestFailure) e.nextElement();
System.out.print(failure.isFailure() ? "Failure: " : "Error: ");
System.out.println(failure.exceptionMessage());
System.out.println(failure.trace());
}
...
無論是Failure或Error,都使用TestFailure封裝,可 使用isFailure()來判斷是否為Failure。