繼 續 自由組合測試 的故事,測試人員又在抱怨了,每次要寫測試,就得自己實作一個Test介面,著實有點麻 煩,你想了一下,也對!要不這樣好了,測試人員只要在某個類別中寫個公開、不傳回值、沒有參數的testXXX()方法, 你的執行器自動找出當中的方法並執行如何?而且你比他多考慮了一些東西,所以,你先設計了一個TestCase:
package
test.cc.openhome;
import java.lang.reflect.*;
public class TestCase extends Assert implements Test {
private String fName;
public TestCase() {}
public TestCase(String name) {
fName = name;
}
protected void setUp() {}
protected void tearDown() {}
@Override
public void run() {
setUp();
runTest();
tearDown();
}
public void runTest() {
Method runMethod= null;
try {
runMethod= getClass().getMethod(fName, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!Modifier.isPublic(runMethod.getModifiers())) {
throw new RuntimeException("方法 \"" + fName + "\" 必須是 public");
}
try {
runMethod.invoke(this, new Class[0]);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getName() {
return fName;
}
public void setName(String name) {
fName = name;
}
}
import java.lang.reflect.*;
public class TestCase extends Assert implements Test {
private String fName;
public TestCase() {}
public TestCase(String name) {
fName = name;
}
protected void setUp() {}
protected void tearDown() {}
@Override
public void run() {
setUp();
runTest();
tearDown();
}
public void runTest() {
Method runMethod= null;
try {
runMethod= getClass().getMethod(fName, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!Modifier.isPublic(runMethod.getModifiers())) {
throw new RuntimeException("方法 \"" + fName + "\" 必須是 public");
}
try {
runMethod.invoke(this, new Class[0]);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getName() {
return fName;
}
public void setName(String name) {
fName = name;
}
}
測試人員要撰寫各個測試案例的話,要繼承TestCase實作子類別,每個測試案 例實作一個testXXX()方法。例如:
package
test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorPlusMinusTest extends TestCase {
public CalculatorPlusMinusTest(String name) {
super(name);
}
public void testPlus() {
Calculator calculator = new Calculator();
int expected = 5;
int result = calculator.plus(3, 2);
assertEquals(expected, result);
}
public void testMinus() {
Calculator calculator = new Calculator();
int expected = 1;
int result = calculator.minus(3, 2);
assertEquals(expected, result);
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.add(new CalculatorPlusMinusTest("testPlus"));
suite.add(new CalculatorPlusMinusTest("testMinus"));
return suite;
}
public static void main(String[] args) {
TestRunner.run(suite());
}
}
import cc.openhome.Calculator;
public class CalculatorPlusMinusTest extends TestCase {
public CalculatorPlusMinusTest(String name) {
super(name);
}
public void testPlus() {
Calculator calculator = new Calculator();
int expected = 5;
int result = calculator.plus(3, 2);
assertEquals(expected, result);
}
public void testMinus() {
Calculator calculator = new Calculator();
int expected = 1;
int result = calculator.minus(3, 2);
assertEquals(expected, result);
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.add(new CalculatorPlusMinusTest("testPlus"));
suite.add(new CalculatorPlusMinusTest("testMinus"));
return suite;
}
public static void main(String[] args) {
TestRunner.run(suite());
}
}
有好一些嗎?好像是有,至少不用分別建立Test的實作類別了,然而你還可以讓測試人員更方便一些。你想在執行測試時,用每個testXXX()方法名稱 來建構TestCase的子類別實例,而後運用反射(Reflection)來執行那些testXXX()方法, 讓測試人員不用自己去作一些建立物件的動作。為此,你要重構TestSuite:
package test.cc.openhome;
import java.lang.reflect.*;
import java.util.*;
public class TestSuite implements Test {
private List<Test> tests = new ArrayList<Test>();
public TestSuite() {}
public TestSuite(Class clz) {
// 找出每個test開頭的方法
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
if (Modifier.isPublic(method.getModifiers())
&& method.getName().startsWith("test")) {
Constructor constructor = null;
try {
constructor = clz.getConstructor();
// 建立TestCase實例
TestCase testCase = (TestCase) constructor.newInstance();
// 設定要呼叫的testXXX()方法
testCase.setName(method.getName());
// 加入測試
add(testCase);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
@Override
public void run() {
for (Test test : tests) {
test.run();
}
}
public void add(Test test) {
tests.add(test);
}
public void add(Class clz) {
tests.add(new TestSuite(clz));
}
}
import java.lang.reflect.*;
import java.util.*;
public class TestSuite implements Test {
private List<Test> tests = new ArrayList<Test>();
public TestSuite() {}
public TestSuite(Class clz) {
// 找出每個test開頭的方法
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
if (Modifier.isPublic(method.getModifiers())
&& method.getName().startsWith("test")) {
Constructor constructor = null;
try {
constructor = clz.getConstructor();
// 建立TestCase實例
TestCase testCase = (TestCase) constructor.newInstance();
// 設定要呼叫的testXXX()方法
testCase.setName(method.getName());
// 加入測試
add(testCase);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
@Override
public void run() {
for (Test test : tests) {
test.run();
}
}
public void add(Test test) {
tests.add(test);
}
public void add(Class clz) {
tests.add(new TestSuite(clz));
}
}
你的TestRunner也 作了些小小的修改:
package
test.cc.openhome;
public class TestRunner {
public static void run(Test test) {
test.run();
}
public static void run(Class clz) {
run(new TestSuite(clz));
}
}
public class TestRunner {
public static void run(Test test) {
test.run();
}
public static void run(Class clz) {
run(new TestSuite(clz));
}
}
現在,如果測試人員要寫測試,只需要:
package
test.cc.openhome;
import cc.openhome.Calculator;
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) {
TestRunner.run(CalculatorTest.class);
}
}
import cc.openhome.Calculator;
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) {
TestRunner.run(CalculatorTest.class);
}
}
省事多了,而且注 意到setUp()與tearDown()的使用,由於TestCase中的run()設計為每次都會呼叫setUp()、testXXX()、 tearDown(),所以setUp()與tearDown()可 用來作為每個testXXX()方法準備測試裝備(Test fixture),這應該算是 Template Method 模式 的實現。
現在測試人員撰寫測試時,彈性也多了許多,可以使用TestSuite自由地組合測試案例中 某些testXXX()方法,或者是直接運行所有的testXXX()方法,每個測試案例也都可以再結合起來。例如:
package test.cc.openhome;
public class CalculatorAll {
public static Test suite() {
TestSuite suite = new TestSuite();
// 隨便你組合
suite.add(CalculatorPlusMinusTest.suite());
suite.add(CalculatorTest.class);
suite.add(new CalculatorTest("testPlus"));
return suite;
}
public static void main(String[] args) {
TestRunner.run(suite());
}
}
public class CalculatorAll {
public static Test suite() {
TestSuite suite = new TestSuite();
// 隨便你組合
suite.add(CalculatorPlusMinusTest.suite());
suite.add(CalculatorTest.class);
suite.add(new CalculatorTest("testPlus"));
return suite;
}
public static void main(String[] args) {
TestRunner.run(suite());
}
}