第一章 為什么使用單元測(cè)試 1.1 程序員的工作——修改軟件
修改既有代碼是程序員謀生的手段。但是為什么我們需要去修改軟件呢?修改軟件有以下4個(gè)主要起因:
● 修正bug
● 添加新特性(feature)
● 改善設(shè)計(jì)
● 優(yōu)化資源使用
這4項(xiàng)都與軟件的“行為”密切相關(guān),見下表。
| 軟件的既有行為 | 軟件的新行為 |
修正bug | 改變軟件的既有行為 | 增加新行為 |
添加新特性 | 保持軟件的既有行為,完全不修改既有代碼 | 無 |
改善設(shè)計(jì) | 保持軟件的既有行為,但軟件的可維護(hù)性得到提升 | 無 |
優(yōu)化資源使用 | 保持軟件的既有行為,但軟件的性能得到提升 | 無 |
通過這張表格我們看出:只有在修正bug時(shí),我們才需要改變軟件的既有行為,而在其他情況下,我們都需要保持住軟件的既有行為。如果我們?cè)诟纳圃O(shè)計(jì),優(yōu)化,或添加新特性時(shí)改變了軟件的既有行為,那我們實(shí)際上是給軟件引入了bug。
可是程序員的工作就是修改軟件,所以我們有很多的“機(jī)會(huì)”給軟件引入bug。有什么辦法能讓我們的生活輕松一點(diǎn),而不用因?yàn)樾薷拇a引入bug而擔(dān)驚受怕嗎?
1.2 軟件夾鉗——測(cè)試
我們已經(jīng)看到:在大多數(shù)情況下,我們希望對(duì)軟件所做的改動(dòng)不會(huì)改變系統(tǒng)的既有行為。即使是對(duì)于修正bug這種情況,我們也希望一旦bug被修正,那么修正后的正確行為能夠得到保持,而不會(huì)被再之后的代碼修改所改變。怎樣做到這一點(diǎn)呢?
讓我們這樣想想:如果我們能在對(duì)代碼進(jìn)行改動(dòng)之前,用一種“軟件夾鉗”(software vise)來固定住軟件的既有行為,那么我們就可以放心大膽地去修改代碼了。那么,又是什么可以來充當(dāng)“軟件夾鉗”呢?答案是:測(cè)試。我們可以這樣想一 下:當(dāng)一段代碼被一組良好的測(cè)試所覆蓋時(shí),我們就可以放手去修改這段代碼,并在修改完成之后立即運(yùn)行這組測(cè)試,來驗(yàn)證我們的修改并沒有改變既有行為而引入 bug。如果確實(shí)改變了既有行為,那么測(cè)試就會(huì)明確無誤地發(fā)出警報(bào)。由此可見,測(cè)試就是程序員所需要的“軟件夾鉗”。
1.3 單元測(cè)試與集成測(cè)試之爭(zhēng)
我們已經(jīng)知道了“測(cè)試”就是程序員所需要的“軟件夾鉗”。但是測(cè)試分為單元測(cè)試和集成測(cè)試,程序員需要哪種測(cè)試呢?讓我們來分析一下程序員需要什么樣的測(cè)試,這樣或許我們就能知道程序員應(yīng)該更偏向于哪種測(cè)試了。
● 程序員需要的測(cè)試應(yīng)該是能幫助程序員定位錯(cuò)誤的,這樣程序員才會(huì)真正地從測(cè)試中得到“實(shí)惠”。
● 程序員需要的測(cè)試應(yīng)該是很容易執(zhí)行的,最好是只需點(diǎn)擊一個(gè)按鈕或鍵入一條命令,這些測(cè)試就能運(yùn)行,而無需費(fèi)時(shí)費(fèi)力地去搭建測(cè)試環(huán)境及準(zhǔn)備測(cè)試數(shù)據(jù)或儀器。
● 程序員需要的測(cè)試應(yīng)該是運(yùn)行速度很快的,最好能在幾分鐘內(nèi)完成,這樣程序員才能快速地得到反饋,采取下一步動(dòng)作。
● 程序員需要的測(cè)試應(yīng)該是易于寫就的,而不愿意對(duì)代碼基大動(dòng)干戈,這樣程序員才會(huì)愿意去寫這些測(cè)試。
● 程序員需要的測(cè)試應(yīng)該是自動(dòng)化的,可重復(fù)的。這樣程序員才會(huì)愿意重復(fù)多次地去運(yùn)行這種測(cè)試。
比對(duì)程序員的需求,我們可以發(fā)現(xiàn)集成測(cè)試往往不能滿足程序員的這些需求:
● 集成測(cè)試由于涉及多個(gè)模塊,因此往往不能提供準(zhǔn)確的錯(cuò)誤定位信息。
● 集成測(cè)試的執(zhí)行時(shí)間一般較長(zhǎng)(小時(shí)級(jí)),這不能給程序員帶來快速反饋。
● 集成測(cè)試可以自動(dòng)化執(zhí)行,但前提是把測(cè)試環(huán)境和測(cè)試數(shù)據(jù)等事先準(zhǔn)備好。
● 集成測(cè)試由于不太容易寫就,通常不是由程序員寫就,而由專門的測(cè)試人員寫就。
相反,單元測(cè)試,尤其是良好的單元測(cè)試,恰恰正是程序員所需要的那種測(cè)試:
單元測(cè)試針對(duì)單個(gè)類或單個(gè)方法,能很有成效地幫助程序員準(zhǔn)確定位問題所在。
單元測(cè)試應(yīng)該是執(zhí)行時(shí)間很短的,全部執(zhí)行也只需5到10分鐘,程序員正好可以去喝喝咖啡。
單元測(cè)試是一種“虛擬”測(cè)試,重在測(cè)試代碼邏輯,因此一般不需要真實(shí)測(cè)試環(huán)境和測(cè)試數(shù)據(jù)的支持。
單元測(cè)試是很容易寫就的,尤其是有單元測(cè)試框架的幫助時(shí)。
由此可見,對(duì)于程序員而言,需要的是單元測(cè)試。程序員使用單元測(cè)試來充當(dāng)軟件夾鉗,并在修改代碼時(shí)獲得快速反饋,從而更有信心地投入到修改軟件的工作中。
1.4 進(jìn)行單元測(cè)試的其他好處
我們已經(jīng)知道了單元測(cè)試帶來的一個(gè)好處:它可以充當(dāng)程序員的“軟件夾鉗”,在程序員修改軟件的過程中給予程序員快速的反饋,幫助程序員定位問題,避免引入bug。單元測(cè)試就只有這一個(gè)好處嗎?不是的。下面我們就來看看引入單元測(cè)試帶來的其他好處。
1.4.1 單元測(cè)試是代碼的“活文檔”
讓文檔及時(shí)反映軟件設(shè)計(jì)和代碼的最新情況,這是一個(gè)頗有挑戰(zhàn)性的問題。一種較好的思路是:使用“內(nèi)部”文檔,即把文檔同代碼“拴”在一起。這樣當(dāng)代碼發(fā)生改變的時(shí)候,文檔也能相應(yīng)更新。注釋就是一種內(nèi)部文檔,良好的注釋應(yīng)該反映代碼的最新狀況。
類比來看,單元測(cè)試同樣也是內(nèi)部文檔,因?yàn)閱卧獪y(cè)試本質(zhì)上也是描述了被測(cè)類或被測(cè)方法的行為。對(duì)軟件行為不了解的程序員,可以通過閱讀單元測(cè)試 代碼來理解軟件的行為。同注釋相比,單元測(cè)試還具有一個(gè)更好的特性:它是一種“可執(zhí)行”文檔。如果單元測(cè)試在被執(zhí)行時(shí)無法通過,那么說明要么單元測(cè)試沒有 反映當(dāng)前代碼的真實(shí)狀況,要么說明代碼中有bug。無論哪種情況,程序員都需要修改某一方,以保持兩者的一致。
1.4.2 具有可測(cè)試性的軟件具有更高的質(zhì)量
近年來流行的極限編程方法論推崇“測(cè)試驅(qū)動(dòng)開發(fā)”。我們認(rèn)為,“測(cè)試驅(qū)動(dòng)開發(fā)”并不一定要求必須先有測(cè)試后有代碼,而關(guān)鍵在于要求在設(shè)計(jì)軟件和 實(shí)現(xiàn)編碼時(shí),一定要預(yù)先把軟件的可測(cè)試性考慮周全。這種可測(cè)試性的重要體現(xiàn)就是能夠方便地將單個(gè)類或方法納入單元測(cè)試之中。具有可測(cè)試性的軟件的質(zhì)量往往 高于不具有可測(cè)試性的軟件,為什么這樣說呢?
● 一個(gè)類能夠被方便地納入單元測(cè)試,往往說明這個(gè)類職責(zé)單一,也就是說它滿足“單一職責(zé)原則”。
● 一個(gè)類能夠被方便地納入單元測(cè)試,往往說明它與其他類之間的耦合程度較低,相互依賴性較小,而且很可能滿足“依賴抽象原則”和“開放-封閉原則”。
因此, 具有可測(cè)試性的軟件,也更有可能是滿足良好設(shè)計(jì)原則的軟件,所以往往質(zhì)量更高。