你來寫,我來跑


簡 單的測試每個人都會寫。例如,現在測試人員撰寫測試案例,你負責提供測試案例的執行器(Test runner),讓測試人員可以執行測試案例以觀看測試結果。

如果測試人員看到某個類別:
package cc.openhome;
public class Calculator {

    public int plus(int op1, int op2) {
        ...
    }
}

測試人員撰寫了以下的測試:
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorTest {
    public static void testPlus() {

        Calculator calculator = new Calculator();
        int expected = 3;
        int result = calculator.plus(1, 2);
        if(expected == result) {
            System.out.println("成功!");
        }
        else {
            System.out.printf(
               "失敗,預期為 %d,但是傳回 %d!%n", expected, result);
        }

    }
}


為了讓測試人員可以執行測試,你撰寫了TestRunner給測試人員作為執行器
package test.cc.openhome;

public class TestRunner {
    public static void main(String[] args) {
        CalculatorTest.testPlus();
    }
}


接著,開發人員在 Calculator類別中新增了方法:
package cc.openhome;
public class Calculator {
    ...
    public int minus(int op1, int op2) {
        ...
    }

}

於是測試人員又在CalculatorTest中 撰寫了另一個測試:
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorTest {
    ...

    public static void testMinus() {
        Calculator calculator = new Calculator();
        int expected = 1;
        int result = calculator.minus(3, 2);
        if(expected == result) {
            System.out.println("正確!");
        }
        else {
            System.out.printf(
               "錯誤,傳回 %d,但應該是 %d!%n", result, expected);
        }
    }
}

為了可以執行新的測試案例,你為測試人員修改了TestRunner
package test.cc.openhome;

public class TestRunner {
    public static void main(String[] args) {
        CalculatorTest.testPlus();
        CalculatorTest.testMinus();
    }
}

接著測試人員執行測試,卻發現了結果報告中出現了「成功!」與「正確!」。你檢視測試人員撰寫的程式,發現當中testPlus()與 testMinus()的程式流程類似,但報告訊息格式不一致。身為程式設 計師,馬上就想到重構(Refactor)一下,重複的部份可以提取出來:
package test.cc.openhome;
public class Assert {
    public static void assertEquals(int expected, int result) {
        if(expected == result) {
            System.out.println("正確!");
        }
        else {
            System.out.printf(
               "失敗,預期為 %d,但是傳回 %d!%n", expected, result);
        }
    }
}

你告訴測試人 員,除了TestRunner之外,現在要斷言可以使用Assert上的工具方法。現在測試人員將CalculatorTest修改如下:
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorTest {
    public static void testPlus() {
        Calculator calculator = new Calculator();
        int expected = 3;
        int result = calculator.plus(1, 2);
        Assert.assertEquals(expected, result);
    }
    public static void testMinus() {
        Calculator calculator = new Calculator();
        int expected = 1;
        int result = calculator.minus(3, 2);
        Assert.assertEquals(expected, result);
    }
}

開發人員不斷地在Calculator中加入新的xxx ()方法,而測試人員不斷地在CalculatorTest增加testXxx()方法,而你得在TestRunner中加入新的 CalculatorT.testXXX()方法

你覺得很煩,雖然撰寫TestRunner是 你的職責,但是不想陪測試人員耗下去,因為測試請求是無窮無盡地,絕對沒 道理測試人員想增加一個測試,你就得修改一次TestRunner。於 是你設計了一個介面:
package test.cc.openhome;
public interface Test {
    void run();
}

然後將TestRunner修 改如下
package test.cc.openhome;
import java.util.*;
public class TestRunner {
    private List<Test> tests;
    public TestRunner() {
        tests = new ArrayList<Test>();
    }
    public void add(Test test) {
        tests.add(test);
    }
    public void run() {
        for(Test test : tests) {
            test.run();
        }
    }
}

你想下班了,你告訴測試人員,現在測試案例要實作Test介面。例如:
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorPlusTest implements Test {
    @Override
    public void run() {
        Calculator calculator = new Calculator();
        int expected = 3;
        int result = calculator.plus(1, 2);
        Assert.assertEquals(expected, result);
    }
}

package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorMinusTest implements Test {
    @Override
    public void run() {
        Calculator calculator = new Calculator();
        int expected = 1;
        int result = calculator.minus(3, 2);
        Assert.assertEquals(expected, result);
    }
}

新的TestRunner也 給測試人員了,這個TestRunner可 以讓測試人員自由增加測試案例,然後執行測試:
package test.cc.openhome;
public class CalculatorTest {
    public static void main(String[] args) {
        TestRunner runner = new TestRunner();
        runner.add(new CalculatorPlusTest());
        runner.add(new CalculatorMinusTest());
        runner.run();
    }
}

畢竟,測試案例如何撰寫與添加是由測試人員掌握,應該要有一個執行器,可以單純地接受測試案例並執行。

現在測試人員與開發人員繼續留下來繼續寫他的測試案例,不過你的TestRunner目前不用再修改 了,所以可以下班回家逍遙了。

這其實就是 Command 模式 的實現。

故事往往不會這麼就結束...XD