2012年3月25日 星期日

失敗的單元測試



我一向習慣用 TDD 來寫程式, 也很自豪寫出的程式品質還不賴, 尤其是用TDD開發完成後一定會順便產出一堆 unit test, 我以為若是有另一位開發者中途加入, 他一定很容易看懂我的程式, 既有的 unit test 也能幫助他確認沒有把程式改壞, 上手負擔會比較輕. 但是最近新進弟兄賴瑞的遭遇, 讓我知道我錯了...

賴瑞修改程式, 改變了某個 class 後 unit test 就無法通過了. 這是預期中的結果, 因為 unit test 也要跟著改變才會符合最新的 class 行為. 賴瑞很讚, 不囉嗦馬上就去改我寫的 unit test. 但也馬上遇到問題了, unit test 的 code 實在太難懂了, 他花一天把程式改完, 卻花了兩天修改 unit test...

我原以為是好東西的 unit test 卻變成賴瑞的絆腳石... 這這這..這到底是怎麼一回事?

賴瑞: "單元測試不夠單元"

一個好的 unit test 應該是自成一體, 簡單幾個步驟設定, 驗證一個結果. 但以我寫出的某個 unit test 為例, 要先設定 http server 依序傳回四個回應, 又要設定 mongo db 裡面的兩個 collection 要含有某 document, 驗證的東西又複雜無比, 難怪賴瑞看到眼睛痛.

這是 unit test 的寫得不好嗎? 是, 而且主體程式寫得也糟, 反應在 unit test 上就是這個樣子. 如果我的主體程式沒有直接 用 HttpRequest class 跟 http server 溝通, 而是透過某個 remote interface 跟 http server 要資料, 那 unit test 中就可以用 mock object 來代替 http server, 同理也可套用到 mongo db 上. 若是我好好遵照這個原則, unit test 一定簡單易讀易改多了, 每個 test 少個 30行程式碼吧.

unit test 的程式碼也是程式碼

希望 unit test 也易讀易懂易維護, 那對待 unit test 程式碼也要像一般的程式碼一樣. 寫一般程式的時候, 只要 copy & paste 程式碼, 腦中警鈴就會響起 -

有重複的程式碼, 這是萬惡根源, 等等要 refactor!

但copy & paste unit test 時, 這警鈴卻昏迷了, 於是我的 unit test 越來越可怕...

我寫一般程式的時候, function 命名都還蠻計較的, 會儘量想個名實相符的東西. 在命名 test function 時我卻滿偷懶的, 有時會寫出這樣的 function 名 - testUploadHandler2..... (烏鴉飛)

My bad may bad my bad .....

武器越大隻, 後座力也越大

TDD 的愛用者, 若你也跟我一樣會偷懶不把 dependency 用 interface 區隔, 不注意 unit test 程式碼的品質, 那別人修改你的程式的時候會遇到更大的困難, 因為除了程式碼難改, unit test 更難改. 慎之 慎之