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