關於 TestCase


使用 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("單元測試尚未撰寫");
}

IDE自動程 式碼產生測試案例時,通常都會自動填上fail(),以提醒開發人員尚有單元測試未撰寫。fail()使用的另一個例子,就是測試例外是否發生。例如:
public void testSomeException() {
    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()(或指定的測試方法)前後執行 之。

在執行測試時,你會發現到有
FailureError兩種測試尚未通過的訊息。

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);
}

之後透過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