先寫單元測試的12個好處!

「寫測試的好處是什麼?」

這個問題非常難回答。通常只能得到「只有寫了才知道」這種含糊不清的答案。

我最近找到Tim King在2006年的文章,非常完整的回答這個問題。

看過之後覺得獲益良多,翻譯出來和大家分享。


先寫單元測試的12個好處!

為什麼工程師會討厭寫測試?為什麼他們會拒絕先寫測試?不用解釋了,那些藉口我全聽過。我知道真正的原因為何。

大部份的工程師根本沒認真試過測試先行。不然就是當下的環境不支持他們寫,導致他們不知道自己在幹嘛。前者的情況居多。最後他們就找藉口:「我們沒時間寫單元測試」、「單元測試不能完全保證程式碼品質」。這是在替自己的悲慘找理由,而不是真的覺得不寫比較好。有趣的是,當這種人直接在你面前開發,你常會看到他開發得很不順,進度常常在開倒車。目睹這種事滿有趣的。但他依舊不會收回原本的看法、依舊不願意先寫測試。這種堅持根本有害。

Kent Beck在《Test Driven Development By Example》書中提到,測試先行有3個步驟:

1. Red:寫個能表達你打算如何使用那段code的測試,還有你期待它做什麼。這個測試會失敗。很多介面會用紅色訊息來表示它。
2. Green:寫出足夠的code來讓那個測試成功,但別多寫。如果你想寫更多code,像是檢查某些錯誤的話,那就先另寫一個測試表達它。當下只要寫剛好夠的code去通過測試即可。
3. Refactor:把多餘的code清理一下,然後改善整體設計。之後再跑一次測試,確保沒弄壞什麼地方。

重複這些步驟直到功能做完。這個流程超級簡單。為什麼工程師會畏懼它?因為這會逼他們從根本上改變開發習慣。

我們認為自己在寫code前不需要先去思考,這些code到底要做什麼。

你如何解決一個軟體問題?學校是怎麼教的呢?第一步怎麼走?你大概只想著如何解決問題本身。你心想:「我要寫哪些code來實作出解決方案?」其實你不應該先想「我要寫哪些code」,你要先想「我要怎樣才能確定問題已經被解決了?」

我們直覺認為一段code的正確與否,只要執行一次就知道了,超明顯的,何必寫那種根本廢話的測試?就是這種這種根深蒂固的想法,導致大部份的人改不了開發習慣。

成功跨過那道鴻溝的人,可以感受到下列幾項好處。我全部體會過。不用完全信我,你自己試試看就知道了。

1. 單元測試保證你的code真的能動

這會讓bug減少。當然,單元測試不能取代系統測試跟驗收測試。但單元測試能補足它們的短處。

2. 你會得到一組底層的regression-test suite

這讓你隨時可以回頭去檢查有否哪邊壞掉、bug在哪。很多團隊會每天把整組測試跑一次。這讓你在把程式交給品管部門之前,可以很輕鬆的把bug抓出來。

3. 讓你改善系統設計的時候,不怕弄壞系統

其實就是測試先行3步驟的第3步。通常測試先行寫出來的code不太需要重構。我看過很多超糟糕的系統,就像精神病患一樣,根本無法搞定。如果有準備好單元測試,你就可以對系統裡面最難搞的部份做出有效的重構。

4. 寫測試會讓coding更好玩

你會先搞懂自己的code要做什麼。然後再讓它完成任務。就算系統還沒全做完,你還是能看到code真的動起來,而且真的沒出錯。你會得到一種「我完成了!」的感覺。每分鐘都會不斷感受到喔。只要試試測試先行,你就會整個人high起來、對自己的作品感到驕傲、被激勵去完成更多事情。

5. 它們可靠地展現目前進度

你不用為了等整個系統組裝起來而多等一個月。在系統完成之前你就能展示進度了。不但能說自己寫了code,還能真的跑給別人看。傳統開發有件事搞錯了。「完成」不等於你寫了code然後丟出去。「完成」應該是你的code能在系統裡跑,而且沒bug。寫測試會讓你更接近這點。

6. 單元測試是一種使用範例

我們都碰過那種不知道怎麼用的library。通常我們會先去找範例程式碼。使用範例可算是一種文件。但公司內部的code通常不會有範例可看。所以只好慢慢試、在系統內東找西找了。因為那個同事可能根本離職了,想問他都沒辦法。單元測試可以當作一種文件。當你不知道Foo類別怎麼用,去看一下單元測試怎麼寫的即可。

7. 測試先行會強迫你寫code前先做規劃

先寫測試會逼你在動手開發前把必須完成的事和整體設計想過一遍。不但讓你更專注,還能讓設計更漂亮。

8. 先寫測試能減少bug的成本

越早發現bug越容易修。之後出現的bug通常是改了好幾個地方才出現的,
導致很難抓出哪裡導致了bug。一開始先找出bug在哪,然後要重新回想這段code是怎麼寫的,因為可能是幾個月前寫的。最後才終於弄懂,搞出一套解法。只要能減少抓bug以及修好bug的時間,幾乎都算大賺。如果在成品交給品管部門或是顧客之前,我們只花幾天就找出bug,通常算是很幸運。那幾花幾分鐘就找出bug呢?測試先行就能做到這點。

9. 它比代碼檢查的效果好

有人說事前代碼檢查比事後測試系統更好,因為成本比較低。在系統完成之後才測試系統,要修好bug可說是麻煩多了。越早發現bug,就越簡單、越便宜、越好搞定。代碼檢查的好處就在這:只花幾天就能抓出bug,不需要等幾個月。但是測試先行成本更低。只要幾分鐘就抓出bug,連幾天都不用。

10. 幾乎解決了「開發者瓶頸」(coder’s block)

不知道下一行寫什麼嗎?就跟「作家瓶頸」(writer’s block)一樣,開發者瓶頸很可能是個大問題。測試先行有系統地處理開發上關於結構的部份,讓你能專心在需要創造的部份。你可能會卡在下段code不知道怎麼測、該怎麼通過測試,但你永遠不會因為下一步卡住。通常會有完全相反的結果:你很想在累倒之前休息一下,但因為清楚看到前面的錄了,所以根本不想停下來。

11. 單元測試讓設計更棒

測試一小塊code會強迫你定義清楚那段code負責什麼。如果測起來很簡單,就表示它的責任很明確,cohesion很高。如果一段code能被單元測試,那就表示它很容易就能放進系統之中,就跟它很容易放進測試之中一樣。它跟相關的code只具有loose coupling 。 High cohesion與loose coupling代表了出色、好維護的設計。容易測試的code也很容易維護。

12. 寫測試會讓開發速度更快

不寫單元測試也許會讓速度更快,但無法保證code真的能跑。開發上會花一堆時間在在事後的修bug。測試先行會消除這類的浪費,從一開始就做對、讓bug更好修。

就算好處這麼多,很多工程師還是繼續維持他們的老樣子。如果你在組織裡極度重視流程,你跟他們一部份人會起衝突。我只能祝你好運。記住一件事,人們不會因為一個東西聽起來不錯就買帳。他們只有在極度渴望、超想得到手來品嚐時才會買帳。希望以上幾點可以幫助你說服他們。

不過,如果你是前者,也就是那種頑固的工程師,不在乎好的軟體設計,只在乎堅持己見…。嗯,我覺得你還真可憐。

(Photo via 8#X, CC licensed)