如何運行該測試呢?手工的方法是鍵入如下命令:
[Windows] D:>java junit.textui.TestRunner testCar
[Unix] % java junit.textui.TestRunner testCar
別擔心你要敲的字符量,以后在IDE中,只要點幾下鼠標成了。運行結果應該如下所示,表明執(zhí)行了一個測試,并通過了測試:
.
Time: 0
OK (1 tests)
如果我們將Car.getWheels()中返回的的值修改為3,模擬出錯的情形,則會得到如下結果:
.F
Time: 0.16
FAILURES!!!
Test Results:
Run: 1 Failures: 1 Errors: 0
There was 1 failure:
1) testCar.testGetWheels "expected:<3> but was:<4>"
注意:Time上的小點表示測試個數,如果測試通過則顯示OK。否則在小點的后邊標上F,表示該測試失敗。注意,在模擬出錯的測試中,我們會得到詳細的測試報告“expected:<3> but was:<4>”,這足以告訴我們問題發(fā)生在何處。下面是你調試,測試,調試,測試...的過程,直至得到期望的結果。
五、Design by Contract(這句話我沒法翻譯)
Design by Contract本是Bertrand Meyer(Eiffel語言的創(chuàng)始人)開發(fā)的一種設計技術。我發(fā)現在JUnit中使用Design by Contract會帶來意想不到的效果。Design by Contract的核心是斷言(assersion)。斷言是一個布爾語句,該語句不能為假,如果為假,則表明出現了一個bug。Design by Contract使用三種斷言:前置條件(pre-conditions)、后置條件(post-conditions)和不變式(invariants)這里不打算詳細討論Design by Contract的細節(jié),而是希望其在測試中能發(fā)揮其作用。
前置條件在執(zhí)行測試之前可以用于判斷是否允許進入測試,即進入測試的條件。如 expectedWheels > 0, myCar != null。后置條件用于在測試執(zhí)行后判斷測試的結果是否正確。如 expectedWheels==myCar.getWheels()。而不變式在判斷交易(Transaction)的一致性(consistency)方面尤為有用。我希望JUnit可以將Design by Contract作為未來版本的一個增強。
六、Refactoring(這句話我依然沒法翻譯)
Refactoring本來與測試沒有直接的聯(lián)系,而是與軟件熵有關,但既然我們說測試能解決軟件熵問題,我們也必須說出解決之道。(僅僅進行測試只能發(fā)現軟件熵,Refactoring則可解決軟件熵帶來的問題。)軟件熵引出了一個問題:是否需要重新設計整個軟件的結構?理論上應該如此,但現實不允許我們這么做。這或者是由于時間的原因,或者是由于費用的原因。重新設計整個軟件的結構會給我們帶來短期的痛苦。而不停地給軟件打補丁甚至是補丁的補丁則會給我們帶來長期的痛苦。(不管怎樣,我們總處于水深火熱之中)
Refactoring是一個術語,用于描述一種技術,利用這種技術我們可以免于重構整個軟件所帶來的短期痛苦。當你refactor時,你并不改變程序的功能,而是改變程序內部的結構,使其更易理解和使用。如:該變一個方法的名字,將一個成員變量從一個類移到另一個類,將兩個類似方法抽象到父類中。所作的每一個步都很小,然而1-2個小時的Refactoring工作可以使你的程序結構更適合目前的情況。Refactoring有一些規(guī)則:
1> 不要在加入新功能的同時refactor已有的代碼。在這兩者間要有一個清晰的界限。如每天早上1-2個小時的Refactoring,其余時間添加新的功能;
2> 在你開始Refactoring前,和Refactoring后都要保證測試能順利通過,否則Refactoring沒有任何意義;
3> 進行小的Refactoring,大的不是Refactoring了。如果你打算重構整個軟件,沒有必要Refactoring了。只有在添加新功能和調試bug時才又必要Refactoring。不要等到交付軟件的后關頭才Refactoring。那樣和打補丁的區(qū)別不大。Refactoring 用在回歸測試中也能顯示其威力。要明白,我不反對打補丁,但要記住打補丁是應該后使用的必殺絕招。(打補丁也需要很高的技術,詳情參看微軟網站)
七、IDE對JUnit的支持
目前支持JUnit的Java IDE 包括
IDE
方式
分數(1-5,滿分5)
Forte for Java 3.0 Enterprise Edition
plug-in
3
Jbuilder 9 Enterprise Edition
integrated with IDE
4
Visual Age for Java
support
N/A
在IDE中如何使用JUnit,是非常具體的事情。不同的IDE有不同的使用方法。一旦理解了JUnit的本質,使用起來十分容易了。所以我們不依賴于具體的IDE,而是集中精力講述如何利用JUnit編寫單元測試代碼。心急的人可參看資料。
八、小結
你一旦安裝完JUnit,有可能想試試我們的Car和testCar類,沒問題,我已經運行過了,你得到的結果應該和我列出的結果類似。接下來,你可能會先寫測試代碼,再寫工作代碼,或者相反,先寫工作代碼,再寫測試代碼。我更贊成使用前一種方法:先寫測試代碼,再寫工作代碼。因為這樣可以使我們編寫工作代碼時清晰地了解工作類的行為。
要注意編寫一定能通過的測試代碼(如文中的例子)并沒有任何意義,只有測試代碼能幫助我們發(fā)現bug,測試代碼才有其價值。此外測試代碼還應該對工作代碼進行全面的測試。如給方法調用的參數傳入空值、錯誤值和正確的值,看看方法的行為是否如你所期望的那樣。
你現在已經知道了編寫測試類的基本步驟:
1> 擴展TestCase類;
2> 覆蓋runTest()方法(可選);
3> 寫一些testXXXXX()方法。
Fixture
接下來的問題是,如果你要對一個或若干個的類執(zhí)行多個測試,該怎么辦?JUnit對此有特殊的解決辦法。如果需要在一個或若干個的類執(zhí)行多個測試,這些類成為了測試的context。在JUnit中被稱為Fixture(如testCar類中的 myCar 和 expectedWheels )。當你編寫測試代碼時,你會發(fā)現你花費了很多時間配置/初始化相關測試的Fixture。將配置Fixture的代碼放入測試類的構造方法中并不可取,因為我們要求執(zhí)行多個測試,我并不希望某個測試的結果意外地(如果這是你要求的,那另當別論了)影響其他測試的結果。通常若干個測試會使用相同的Fixture,而每個測試又各有自己需要改變的地方。為此,JUnit提供了兩個方法,定義在TestCase類中。
protected void setUp() throws java.lang.Exception
protected void tearDown() throws java.lang.Exception
覆蓋setUp()方法,初始化所有測試的Fixture(你甚至可以在setUp中建立網絡連接),將每個測試略有不同的地方在testXXX()方法中進行配置。覆蓋tearDown()(我總想起一首叫雨滴的吉他曲),釋放你在setUp()中分配的性資源,如數據庫連接。當JUnit執(zhí)行測試時,它在執(zhí)行每個testXXXXX()方法前都調用setUp(),而在執(zhí)行每個testXXXXX()方法后都調用tearDown()方法,由此保證了測試不會相互影響。
TestCase
需要提醒一下,在junit.framework.Assert類中定義了相當多的assert方法,主要有assert(),assertEquals(), assertNull(), assertSame(), assertTrue(), fail()等方法。如果你需要比較自己定義的類,如Car。assert方法需要你覆蓋Object類的equals()方法,以比較兩個對象的不同。實踐表明:如果你覆蓋了Object類的equals()方法,好也覆蓋Object類的hashCode()方法。再進一步,連帶Object類的toString()方法也一并覆蓋。這樣可以使測試結果更具可讀性。
當你設置好了Fixture后,下一步是編寫所需的testXXX()方法。一定要保證testXXX()方法的public屬性,否則無法通過內。╮eflection)對該測試進行調用。每個擴展的TestCase類(也是你編寫的測試類)會有多個testXXX()方法。一個testXXX()方法是一個測試。要想運行這個測試,你必須定義如何運行該測試。如果你有多個testXXX()方法,你要定義多次。JUnit支持兩種運行單個測試的方法:靜態(tài)的和動態(tài)的方法。