1. 自信的編碼
有一次——或許是上個禮拜二——有兩個開發者:Pat 和Dale。他們面臨著相同的后期限,而這也越來越近了。Pat 每天都在著急地編寫代碼,寫完一個類又寫一個類,寫完一個函數又接著寫另一個函數,還經常不得不停下來做一些調整,使得代碼能夠通過編譯。
Pat 一直保持著這種工作方式,直到后期限的前。而這時已經是演示所有代碼的時候了。Pat 運行了上層的程序,但是一點輸出也沒有,什么都沒有。這時只好用調試器來單步跟蹤了。“Hmm,決不可能是這樣的”,Pat 想,“此時這個變量不是0 啊”。于是,Pat 只能回過頭來看代碼,嘗試著跟蹤一下這個難以琢磨的程序的調用流程。
時間已經越來越晚了,Pat 找到并且糾正了這個bug;但在這個過程中,Pat 又找到了其他好幾個bug;如此幾次過后,bug 還是存在。而程序輸出那邊,仍然沒有結果。這時,Pat 已經筋疲力盡了,完全搞不清楚為什么會這樣,認為這種(沒有輸出的)行為是毫無道理的。
而于此同時,Dale 并沒像Pat 那么快地寫代碼。Dale 在寫一個函數的時候,會附帶寫一個簡短的測試程序來測試這個函數。這里沒有什么特殊的地方,只是添加了一個簡單的測試,來判斷函數的功能是否和程序員期望的一致。顯然,考慮如何寫,然后把測試寫出來,是需要占用一定時間的;但是Dale 在未對剛寫的函數做出確認之前,是不會接著寫新代碼的。也是說,只有等到已知函數都得到確認之后,Dale 才會繼續編寫下一個函數,然后調用前面的函數等等。
在整個過程中,Dale 幾乎不使用調試器;而且對Pat 的模樣也有些困惑不解:只見他頭埋在兩手之間,嘀咕著各種難聽的話語,咒罵著計算機,充血的眼球同時盯著好幾個調試窗口。
后期限終于到了,Pat 未能完成任務。而Dale 的代碼被集成到整個系統中,并且能夠很好地運行。之后,在Dale 的模塊中,出現了一個小問題;但是Dale 很快發現了問題所在,在幾分鐘之內解決了問題。
現在,是該總結一下上面這個小故事的時候了:Dale 和Pat 的年紀相當,編碼能力相當,智力也差不多。的區別是Dale 非常相信單元測試;對于每個新寫的函數,在其他代碼使用這個函數并對它形成依賴之前,都要先做單元測試。
而Pat 則沒有這么做,他總是“知道”代碼的行為應該和所期望的完全一樣,并且等到所有代碼都差不多寫完的時候,才想起來運行一下代碼。然而到了這個時候,要想定位bug,或者,甚至是確定哪些代碼的行為是正確的,哪些代碼的行為是錯誤的,都為時已晚了。
2. 什么是單元測試
單元測試是開發者編寫的一小段代碼,用于檢驗被測代碼的一個很小的、很明確的功能是否正確。通常而言,一個單元測試是用于判斷某個特定條件(或者場景)下某個特定函數的行為。例如,你可能把一個很大的值放入一個有序list 中去,然后確認該值出現在list 的尾部。或者,你可能會從字符串中刪除匹配某種模式的字符,然后確認字符串確實不再包含這些字符了。
執行單元測試,是為了證明某段代碼的行為確實和開發者所期望的一致。
對于客戶或終使用者而言,這種測試必要嗎,它與驗收測試有關嗎?這個問題仍然很難回答。事實上,我們在此并不關心整個產品的確認、驗證和正確性等等;甚至此時,我們都不去關心性能方面的問題。我們所要做的一切是要證明代碼的行為和我們的期望一致。因此,我們所要測試的是規模很小的、非常獨立的功能片斷。通過對所有單獨部分的行為建立起信心,確信它們都和我們的期望一致;然后,我們才能開始組裝和測試整個系統。
畢竟,要是我們對手上正在寫的代碼的行為是否和我們的期望一致都沒把握,那么其他形式的測試也都只能是浪費時間而已。在單元測試之后,你還需要其他形式的測試,有可能是更正規的測試,那一切都要看環境的需要來決定了。總之,做測試如同做善事,總是要從家(代碼基本的正確性)開始。
3. 為什么要使用單元測試
單元測試不但會使你的工作完成得更輕松,而且會令你的設計變得更好,甚至大大減少你花在調試上面的時間。
在我們上面的小故事里面,Pat 因為假設底層的代碼是正確無誤的而卷入麻煩之中,先是高層代碼中使用了底層代碼;然后這些高層代碼又被更高層的代碼所使用,如此往復。在對這些代碼的行為沒有任何信心的前提下,Pat 等于是在假設上面用豎立卡片堆砌了一間房子——只要將下面卡片輕輕移動,整間房子會轟然倒塌。
當基本的底層代碼不再可靠時,那么必需的改動無法只局限在底層。雖然你可以修正底層的問題,但是這些對底層代碼的修改必然會影響到高層代碼,于是高層代碼也連帶地需要修改;以此遞推,很可能會動到更高層的代碼。于是,一個對底層代碼的修正,可能會導致對幾乎所有代碼的一連串改動,從而使修改越來越多,也越來越復雜。于是,整間由卡片堆成的房子由此倒塌,從而使整個項目也以失敗告終。
Pat 總是說:“這怎么可能呢?”或者“我實在想不明白為什么會這樣”。如果你發現自己有時候也會有這種想法,那么這通常是你對自己的代碼還缺乏足夠信心的表現——你并不能確認哪些是工作正常的而哪些不是。
為了獲得Dale 所具有的那種對代碼的信心,你需要“詢問”代碼究竟做了什么,并檢查所產生的結果是否確實和你所期望的一致。
這個簡單的想法描述了單元測試的核心內涵:這個簡單有效的技術是為了令代碼變得更加完美。