文章出處:Matrix 作者:Totodo翻譯 發(fā)布時(shí)間:2005-11-10
自動(dòng)化測(cè)試(automated testing)在好多書(shū)籍中被介紹了,但很少注意講怎樣去組織這些測(cè)試。 當(dāng)測(cè)試寫(xiě)的越多時(shí),很難知道把這些測(cè)試放到哪或者用什么去調(diào)用它們。 在極限編程---Extreme Programming(xp),測(cè)試驅(qū)動(dòng)開(kāi)發(fā) Test-Driven Development (TDD)盛行的時(shí)代,這成了一個(gè)很大的問(wèn)題。 你可以把 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)認(rèn)為是"Development through testing" 開(kāi)發(fā)由經(jīng)測(cè)試。
TDD的主要條款:
在任何代碼片段之前,必須先寫(xiě)好自動(dòng)檢測(cè)這段代碼功能的程序。既然代碼不存在,那么測(cè)試在一開(kāi)始就失敗。
在 測(cè)試通過(guò)之后,復(fù)制的代碼必須刪掉。
象這樣的方式每個(gè)程序員都可以應(yīng)用,并不需要特定的方法論。但在我們開(kāi)始寫(xiě)test之前, 值得我們注意的是,先考慮一下如何組織自動(dòng)化測(cè)試。
這里有幾種我們需要考慮的測(cè)試
單元測(cè)試(Unit test) :這些是為檢查個(gè)別模塊(比如classes類)服務(wù)的。 如果對(duì)象需要訪問(wèn)外部的數(shù)據(jù)源,比如Database,就需要通過(guò)一些模擬的對(duì)象(MOCK object)來(lái)模擬Database, (但這也只有在真實(shí)環(huán)境的數(shù)據(jù)與測(cè)試環(huán)境不同的時(shí)候。
比如測(cè)試環(huán)境里面沒(méi)有真實(shí)Datebase,就需要MOCK Object)
用戶測(cè)試 (Customer‘s test):這里是功能的,系統(tǒng)的并且認(rèn)可的測(cè)試。系統(tǒng)中所有的行為檢查都做為一個(gè)整體。 在XP理論中,這些測(cè)試,是由用戶編寫(xiě)的,給出測(cè)試案例提綱。
集成測(cè)試 (Itegration tests): 這些測(cè)試象是在用戶測(cè)試和單元測(cè)試之間的十字路口。 集成測(cè)試幫助程序測(cè)試幾個(gè)級(jí)別中交互。 ,Mock Object不會(huì)出現(xiàn)在集承測(cè)試中,他會(huì)增加測(cè)試時(shí)間。同樣,集成測(cè)試也經(jīng)常需要存在的特定的測(cè)試環(huán)境,比如從數(shù)據(jù)庫(kù)中放一些測(cè)試數(shù)據(jù)。集成測(cè)試也許使用外部的lib。 Cactus就是這樣一個(gè)J2EE集成的lib。 解釋這些測(cè)試已經(jīng)超出了本篇文章的范圍,并且也需要詳細(xì)的理論敘述,所以,你僅需要知道這種測(cè)試存在就可以了。
開(kāi)發(fā)測(cè)試(Developer‘s test) : 這種測(cè)試就是那些開(kāi)發(fā)者校驗(yàn) 整段代碼,新加的代碼,新加的函數(shù)函數(shù)。 對(duì)于每個(gè)開(kāi)發(fā)而言, 隨時(shí)生成新的的測(cè)試去檢查代碼是很重要的。 組織這些測(cè)試和組織這些代碼有著同樣的重要性。
至于本文其他地方,只要說(shuō)到"測(cè)試",就是專指開(kāi)發(fā)測(cè)試(Developer‘s test)。
在開(kāi)發(fā)期間, 一個(gè)程序員有時(shí)可能問(wèn)自己:系統(tǒng)中這個(gè)行為有test么,這個(gè)test存在么,哪里可以找到這個(gè)test?每次發(fā)現(xiàn)錯(cuò)誤,都是靠最基礎(chǔ)修改bug而不是通過(guò)自動(dòng)測(cè)試,這是一個(gè)典型的例子。 在這種情形下事情進(jìn)展可能是:
去找到這個(gè)函數(shù)的測(cè)試(可能測(cè)試已經(jīng)寫(xiě)了,但里面還有一些小錯(cuò)誤)
如果這樣的測(cè)試還沒(méi)有,或者測(cè)試不能蓋住這種錯(cuò)誤,我們就寫(xiě)一個(gè)新的測(cè)試來(lái)蓋住這種錯(cuò)誤。
現(xiàn)在 我們深信,程序在新的測(cè)試中不會(huì)通過(guò)。
修復(fù)程序中的bug。
再運(yùn)行測(cè)試
確定程序在測(cè)試中通過(guò)了。
當(dāng)然,可能出現(xiàn)各種各樣的處理, 但思想必須很明確:你只需糾正那些被測(cè)試找出那些錯(cuò)誤。
現(xiàn)在,讓我們告訴你一個(gè)開(kāi)發(fā)人員怎樣解決這種情形。 通過(guò)存在的功能性的測(cè)試
我利用一些集成的開(kāi)發(fā)環(huán)境(IDE)來(lái)查找 被修正那些類和方法的放在什么地方。
制造一個(gè)已知的錯(cuò)誤環(huán)境,來(lái)查找那些代碼判斷存在錯(cuò)誤。
最后但不是最不重要的,寫(xiě)好測(cè)試并且放到一個(gè)現(xiàn)有的測(cè)試類中去。 如果你不小心出了錯(cuò)誤, 期望你和你的同事能注意到副本,并且糾正它
都準(zhǔn)就緒,開(kāi)始建立測(cè)試了, 所以現(xiàn)在需要給測(cè)試取一個(gè)名稱。 你可能說(shuō),“這不是問(wèn)題: 在每個(gè)類面前加個(gè)Test就是了!” 但并不是那么簡(jiǎn)單的, 讓我告訴你這樣如果可能造成的問(wèn)題:
當(dāng)時(shí)候我們?cè)谑褂肨DD的方式開(kāi)發(fā)時(shí), 需要測(cè)試的class或者method可能都不存在。
也可能一個(gè)test 含蓋了好幾個(gè)方法,甚至好幾個(gè)classes。
這些僅僅是最普通的問(wèn)題, 下面還有更多。
給個(gè)在test命名上的建議: test 類的取名首先應(yīng)該表達(dá)出這個(gè)類是一個(gè)test類,并且能確切的表示出他要檢查哪些,留有這個(gè)原class名的味道。 其實(shí)這很容易,請(qǐng)別擔(dān)心這個(gè)名稱會(huì)變的很長(zhǎng)或者很丑陋,自己隨便怎樣取都可以。
下面我們將使用Eclipse中的JUnit工具建立我們的第一個(gè)測(cè)試,假定你已經(jīng)下載了這個(gè)產(chǎn)品的當(dāng)前版本, 如果沒(méi)有,你隨時(shí)可以從它的官方網(wǎng)站(www.eclipse.org)下載。我們需要JUnit,你也可以從它的官方網(wǎng)站(www.junit.org)上下載,下載并解壓縮到你硬盤中存放java libaries的地方。
打開(kāi)Eclipse.我們將建立一個(gè)新的工程的工作空間(workplace project) 點(diǎn) File -> New ->Project,選擇Java一路Next。 輸入工程名稱(project name),比如ProjectWithJUnit. 點(diǎn)擊完成。 這樣就建立了一個(gè)新工程,讓我們配置一下我們的Eclipse,于是,我們把JUnit library 添加到build path. 點(diǎn)擊 Project-->Properties, 選擇Java Build Path Libraries, 點(diǎn)Add Exteranal JARs 選中JUnit.jar。 你將會(huì)看到JUnit將會(huì)出現(xiàn)在的屏幕上 libraries列表中。 點(diǎn)Okay,Eclipse將強(qiáng)制rebuild所有的build paths.
我們已經(jīng)準(zhǔn)備好,開(kāi)始寫(xiě)我們的"Hello World"了 . 讓我們遵照TDD規(guī)范:在編碼之前就建立測(cè)試。為了, 我們將假頂我們將要寫(xiě)的類名是HelloWorld 有一個(gè)返回字符串的方法 say().
要建立這樣一個(gè)test, 在ProjectWithJUnit標(biāo)題上右鍵, 選擇New -> Other,展開(kāi)"Java", 選擇JUnit. 在對(duì)話框的右邊一攔里選擇TestCase,接著點(diǎn)Next. 參見(jiàn)圖1。
圖1。 在Eclipse 中建立JUnit test
在Test class:一攔里輸入我們需要測(cè)試的class--HelloWorld。并且給Test case取個(gè)名稱--- 比如,TestThatWeGetHelloWorldPrompt(是的,這看上去太長(zhǎng)了,但是它能很清楚表達(dá)出它的意思) 點(diǎn)Finish完成。
下面是 TestThatWeGetHelloWorldPrompt.java的代碼:
public class TestThatWeGetHelloWorldPrompt
extends TestCase {
public TestThatWeGetHelloWorldPrompt(
String name) {
super(name);
}
public void testSay() {
HelloWorld hi = new HelloWorld();
assertEquals("Hello World!", hi.say());
}
public static void main(String[] args) {
junit.textui.TestRunner.run(
TestThatWeGetHelloWorldPrompt.class);
}
}
這個(gè)代碼一點(diǎn)都不復(fù)雜,僅僅有一點(diǎn)點(diǎn)特別。 不管怎樣,讓我們?cè)敿?xì)的檢查它。 我們繼承了JUnit的TestCase. (TestCase 在JUnit的javadoc里定義是"用來(lái)運(yùn)行多個(gè)Test的固定裝置")。 JUnit也定義了TestSuite 由于一組關(guān)聯(lián)的TestCase組成..
通過(guò)以下兩步來(lái)建立我們簡(jiǎn)單的Test Case;
建立Junit.framework.TestCase的實(shí)例.
定義一些 以"test"開(kāi)頭的測(cè)試函數(shù), 并且返回一空值.(比如 testWasTranscationSuccessful(),testShow()等等).
TestThatWeGetHelloWorldPrompt.java 同時(shí)遵循這些標(biāo)準(zhǔn): 這些TestCase的子類含有一個(gè)testSay()的方法. 這個(gè)方法由assertEquals()方法調(diào)用, 用于檢驗(yàn)say()的返回值(按照這里的做法返回應(yīng)該是不一致,因?yàn)橐婚_(kāi)始建立的HelloWorld 我們讓say()返回的值是null).
main()主函數(shù)是用來(lái)運(yùn)行test并且顯示輸出的結(jié)果. JUnit的TestRunnery以(swing.u)圖形和本文(text.ui)的的方式來(lái)執(zhí)行我們的test并反饋信息。我們就使用文本(text.ui),這個(gè)Eclipse肯定支持. (譯注:這里可能翻譯的不怎么好,所謂文本和圖形,是指你在建立TestCase的時(shí)候,有一個(gè)選項(xiàng),Which method stubs would you like to create,選擇text.ui|| swing.ui||awt.ui,一般是選擇text.ui因?yàn)镋clipse肯定支持這個(gè)), 依照這些文本的信息,Eclipse同時(shí)會(huì)生成圖形顯示。(在Package Exploer的下面Tab條上會(huì)多個(gè)JUnit,點(diǎn)它就看到了:)。
又一個(gè)所以,按照現(xiàn)在這樣測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)的做法, 一旦我們跑起了我們的test,我們應(yīng)該看到返回一些錯(cuò)誤的信息。 點(diǎn)Run-> Run as -> JUnit Test(注意啊, 這個(gè)TestThatWeGetHelloWorldPrompt.java應(yīng)該在Package Explorer被點(diǎn)中,在左邊那個(gè)window中),你點(diǎn)到的因該是JUnit window(就是下面的那個(gè)Tab條,注意不是Package Exploer),這樣你就看到了JUnit window, 他會(huì)顯示一個(gè)紅色條,表示是一個(gè)失敗的Test。 (如果你按了運(yùn)行它沒(méi)有自動(dòng)轉(zhuǎn)到這個(gè)窗口,你可以點(diǎn)做下Tab條 上的JUnit標(biāo)簽。)
一按運(yùn)行, 太好了,果然出錯(cuò)了。好,現(xiàn)在正式開(kāi)始建立用于工作的HelloWorld代碼,---點(diǎn)New->Class,可能和原來(lái)的的重復(fù),那就把原來(lái)的刪掉。代碼如下:
HelloWolrd.java
public class HelloWorld {
public String say() {
return("Hello World!");
}
}
這及為簡(jiǎn)單的,都用不著注釋?,F(xiàn)在再來(lái)測(cè)試一下看看結(jié)果。就用上面的方法,點(diǎn)Run-> Run As Jnit. 在左邊的JUnit窗口中出現(xiàn)了一個(gè)綠條。 看圖三。 出現(xiàn)綠色的條表示測(cè)試通過(guò)了。
現(xiàn)在,再變個(gè)條件,讓測(cè)試不通過(guò)。 這將幫助我們理解JUnit test怎樣覆蓋并且報(bào)出不同的錯(cuò)誤。 編輯 assertEquals()方法,把它的返回值從"Hello World!"變成另外一個(gè)值 比如"Hello ME!". 這樣,當(dāng)你再運(yùn)行這個(gè)JUnit test,那個(gè)顯示條又變成紅的了,并且在Failuer Trace里看到是不是什么導(dǎo)致了錯(cuò)誤。 如圖:
總結(jié)。我想說(shuō)一些自己的想法(這里還是原文不是翻譯過(guò)來(lái)的)。 我過(guò)去并不認(rèn)為測(cè)試代碼是開(kāi)發(fā)過(guò)程中很重要的一部分。 但在最近幾年發(fā)展的很快,多虧了那些方法論(比如基于異常開(kāi)發(fā)"exceptions-based development"等),他們促進(jìn)了測(cè)試以及測(cè)試工具的發(fā)展。