繼 續 建立測試案例 的故事,測試人員又來了:「你的執行器提供的測試訊息怎麼都是文字模式?如果哪天要顯示在視窗模式下怎麼辦?還有!可以多些測試訊息嗎?只顯示成功或失敗 其實沒啥幫助耶!」
他的抱怨也不是沒有道理,那麼,所有的錯誤就先用例外(Exception)丟出好了,到時接住(Catch)例外,再看怎麼顯示訊息好了。所以,你改了 一下Assert類 別:
package
test.cc.openhome;
public class Assert {
public static void assertEquals(String message, int expected, int result) {
if(expected != result) {
if(message == null) {
throw new RuntimeException(String.format(
"失敗,預期為 %d,但是傳回 %d!", expected, result));
}
throw new RuntimeException(String.format(message, expected, result));
}
}
public static void assertEquals(int expected, int result) {
assertEquals(null, expected, result);
}
}
public class Assert {
public static void assertEquals(String message, int expected, int result) {
if(expected != result) {
if(message == null) {
throw new RuntimeException(String.format(
"失敗,預期為 %d,但是傳回 %d!", expected, result));
}
throw new RuntimeException(String.format(message, expected, result));
}
}
public static void assertEquals(int expected, int result) {
assertEquals(null, expected, result);
}
}
你很貼心,這次修改後還可以讓測試人員自訂錯誤訊息。不過接下來比較麻煩,你要收集錯誤訊息,所以你先定義一個TestResult:
package
test.cc.openhome;
import java.util.*;
public class TestResult {
private List<String> messages = new ArrayList<String>();
public void run(TestCase test) {
try {
test.run();
}
catch(Throwable t) {
messages.add(t.getMessage());
}
}
public List<String> getMessages() {
return messages;
}
}
import java.util.*;
public class TestResult {
private List<String> messages = new ArrayList<String>();
public void run(TestCase test) {
try {
test.run();
}
catch(Throwable t) {
messages.add(t.getMessage());
}
}
public List<String> getMessages() {
return messages;
}
}
測試失敗時會丟出例外,例外發生時收集訊息。
接著要改比較多東 西了,因為要讓這個TestResult進入TestCase收集訊息。首先你調整了一下Test介面的定義:
package test.cc.openhome;
public interface Test {
void runTest(TestResult result);
}
public interface Test {
void runTest(TestResult result);
}
由於介面定義改了,實作Test介面的相關類別都得調整一下,以下紅字部份是有調整的:
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 runTest(TestResult result) {
result.run(this);
}
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 {
// invoke 中發生所有例外,一律會用InvocationTargetException包裹
runMethod.invoke(this, new Class[0]);
}
catch(InvocationTargetException e) {
// 這邊要取得InvocationTargetException 中的目標例外,才是真正的錯誤訊息
throw new RuntimeException(this.getClass() + "." +
runMethod.getName() + ": " + e.getTargetException().getMessage());
}
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 runTest(TestResult result) {
result.run(this);
}
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 {
// invoke 中發生所有例外,一律會用InvocationTargetException包裹
runMethod.invoke(this, new Class[0]);
}
catch(InvocationTargetException e) {
// 這邊要取得InvocationTargetException 中的目標例外,才是真正的錯誤訊息
throw new RuntimeException(this.getClass() + "." +
runMethod.getName() + ": " + e.getTargetException().getMessage());
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getName() {
return fName;
}
public void setName(String name) {
fName = name;
}
}
這樣TestCase就 調整好了,由於你還會使用TestSuite來 組合測試,而且它也實作了Test介 面,所以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) {
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) constructor.newInstance();
testCase.setName(method.getName());
add(testCase);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
@Override
public void runTest(TestResult result) {
for (Test test : tests) {
test.runTest(result);
}
}
public void add(Test test) {
tests.add(test);
}
public void add(Class clz) {
tests.add(new TestSuite());
}
public List<Test> get() {
return tests;
}
}
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) {
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) constructor.newInstance();
testCase.setName(method.getName());
add(testCase);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
@Override
public void runTest(TestResult result) {
for (Test test : tests) {
test.runTest(result);
}
}
public void add(Test test) {
tests.add(test);
}
public void add(Class clz) {
tests.add(new TestSuite());
}
public List<Test> get() {
return tests;
}
}
再來是 TestRunner了,原本的TestRunner改一下,建立TestResult,執行測試以收集資訊:
package
test.cc.openhome;
public class TestRunner {
public static TestResult run(Test test) {
TestResult result = new TestResult();
test.runTest(result);
return result;
}
public static TestResult run(Class clz) {
return run(new TestSuite(clz));
}
}
public class TestRunner {
public static TestResult run(Test test) {
TestResult result = new TestResult();
test.runTest(result);
return result;
}
public static TestResult run(Class clz) {
return run(new TestSuite(clz));
}
}
如果要個文字模式的顯示,那就設計個TextTestRunner好了:
package test.cc.openhome;
public class TextTestRunner {
public static void run(Test test) {
TestResult result = TestRunner.run(test);
for(String message : result.getMessages()) {
System.out.println(message);
}
}
public static void run(Class clz) {
run(new TestSuite(clz));
}
}
public class TextTestRunner {
public static void run(Test test) {
TestResult result = TestRunner.run(test);
for(String message : result.getMessages()) {
System.out.println(message);
}
}
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;
@Override
protected void setUp() {
calculator = new Calculator();
}
@Override
protected void tearDown() {
calculator = null;
}
public void testPlus() {
int expected = 2;
int result = calculator.plus(3, 2);
assertEquals(expected, result);
}
public void testMinus() {
int expected = 2;
int result = calculator.minus(3, 2);
assertEquals(expected, result);
}
public static void main(String[] args) {
TextTestRunner.run(CalculatorTest.class);
}
}
import cc.openhome.Calculator;
public class CalculatorTest extends TestCase {
private Calculator calculator;
@Override
protected void setUp() {
calculator = new Calculator();
}
@Override
protected void tearDown() {
calculator = null;
}
public void testPlus() {
int expected = 2;
int result = calculator.plus(3, 2);
assertEquals(expected, result);
}
public void testMinus() {
int expected = 2;
int result = calculator.minus(3, 2);
assertEquals(expected, result);
}
public static void main(String[] args) {
TextTestRunner.run(CalculatorTest.class);
}
}
testPlus() 與testMinus()中的expected我故意寫錯了,所以測試一定會失敗,這時 測試人員會看到的訊息是:
class
test.cc.openhome.CalculatorTest.testPlus: 失敗,預期為 2,但是傳回 5!
class test.cc.openhome.CalculatorTest.testMinus: 失敗,預期為 2,但是傳回 1!
class test.cc.openhome.CalculatorTest.testMinus: 失敗,預期為 2,但是傳回 1!
事實上,這個故事發生前,你若知道有個 JUnit 測試框架,你什麼都不用作,人家都幫你寫好了,而且更完善,JUnit 基本原理都說完了,所以接下來,可以開始介紹 JUnit 了。。XD