TDD 的可行與不可行

上個禮拜參加了彭靖灝老師的研討會(軟體工程從自己做起-軟體工程之千山我獨行), 今天又拜讀了彭老師的「習慣影響思維」。他在研討會及文章中一再強調測試先行的理念, 又深感軟體工程師對於 TDD (Test-driven Development) 的疑慮或者排斥, 不由得心生「千山我獨行」之感...

上個禮拜參加了彭靖灝老師的研討會(軟體工程從自己做起-軟體工程之千山我獨行), 今天又拜讀了彭老師的「習慣影響思維」。他在研討會及文章中一再強調測試先行的理念, 又深感軟體工程師對於 TDD (Test-driven Development) 的疑慮或者排斥, 不由得心生「千山我獨行」之感。

我自己有六年 SQA (Software Quality-Assurance) 經歷, 後來又投入 .Net 開發, 所以對於測試及開發都有蠻深的體會。其實測試本身是個還算重的負擔 (overhead), 我覺得大部份軟體工程師並不真正了解這一點, 至於老闆就更不了解了 (還是裝傻?)。

彭老師在研討會中講了一句話, 讓我當場有點呆掉的感覺: 「當然, 單元測試是由軟體工程師自己來寫」。我承認, 在國內, 單元測試差不多都是交由軟體工程師來做, 對於這個「事實」或者說「慣例」, 我無話可講, 因為它是現況, 我只對那句話前面加上的「當然」兩字稍有意見。

在所有有點份量的測試理論書籍中, 單元測試從來未曾被從測試人員的工作中抽離出去; 因為單元測試永遠是白箱 (White-box) 測試中無法切割的一個單元, 除非整個白箱測試都不應該歸入測試工作的範疇。對於白箱測試, 施行者當然要身兼軟體工程師的角色 (不然怎麼看得懂程式), 但是這些人也是測試工程師呀!

我想彭老師的本意是說「單元測試交由軟體工程師兼著做」, 如果不要加上那個「當然」二字, 我就不會(也不敢)有任何意見。然而, 這種行之有年的現象, 剛好也是我觀察到國內軟體公司的普遍狀況, 也不由得讓我產生相當的無奈感, 那就是「為什麼很難得看到專責的白箱測試團隊」?

單單拿單元測試來談好了。我認為「交由軟體工程師來兼著做」的想法有時恐怕會太簡化了問題。因為既然把它叫做「測試」, 以我 SQA 的角度來思考, 就應該從嚴格的角度來認定! 你以為測試有那麼簡單嗎? 請問, 你的程式裡面有做防呆嗎? Boundry Test 要不要做? Path 要不要分析? 可靠度如何? 效能如何? Exception 有沒有全部被捕捉? 有沒有 log 起來? 如何 report? 有沒有評估 risk? Coverage 如何計算? 怎麼定 test data? 誰來認定 pass/fail? 會不會被攻擊?

把測試工作交給普通的開發工程師來做的結果就不可能嚴謹; 「兼著做」的結果更會變成亂做一通、交差了事。所以, 為什麼白箱測試的工作不應該交給專業的測試工程師來做?

但是話又講回來, TDD 的理想是很祟高, 方向也很正確沒錯, 如果連單元測試都交給專業的測試團隊來執行, 那麼軟體的可靠度勢必可以提昇到一個前所未見的程度。但是代價是什麼? 前題又是什麼?

要能做到那麼嚴謹的 TDD, 由於成本不用說, 一定很高昴, 所以只有在需求非常明確、分析極為徹底的情況之下, 做起來才會划算。但是我真的不知道國內曾經有存在過這種完美的專案。我所看過的軟體專案, 大部份不只是需求不明確而已, 根本是不知道需求為何物。需求多半是 SA 去訪談後 (或者甚至未曾訪談) 自己做決定的, 然後愈接近結案時, 使用者 (其實大部份是長官) 的不同意見會適時的冒出來, 使得軟體最後長得跟原來完全不一樣。

對於一個異動劇烈的案子, 我想我們就不必太去強調 TDD 了。TDD 實際上應該被用在幾乎不會異動的專案上面; 例如在開發自用的專屬元件的時候, 由於我們可以絕對掌握需求、時效和預算, 透過 TDD, 我們可以把產品做到最好。當然, 自己的產品可能會有缺乏預算的問題, 在這裡我們就不適合討論了。

也許有人覺得, 單元測試有什麼難的? 不就是檢測輸出入和功能是否與規格相符就行了嗎? 是的! 如果只是這樣, TDD 的理想當然不難達到。只可惜我站在 SQA 的觀點, 如果只測試到這種程度的話, 這就叫做「隨便測一測」, 而不能稱為嚴謹。舉個例子好了。假設我設計一個函式, 傳兩個數字進來相除, 再把結果傳回去:

int divide(int a, int b) 
{ ... }

如果你的單元測試就只是檢查 "if (divide(10, 5) == 2)" 而已的話, 這就叫做「隨便測一測」, 甚至是「有測等於沒測」了。

稍為嚴謹的做法, 起碼我們要檢查把 0 當做分母傳進去的結果。此外, 如果你丟負數進去會如何? 或許這個程式的目的原本並不預期會有任何負值被丟進來, 萬一真的有, 應該怎麼處理? 是丟個 exception 嗎? 還是捨棄該值, 改回傳 0?

此外, 萬一被丟進來的值是 null 怎麼辦? 如果未處理, 單元測試在 runtime 就會丟 exception 出來, 但如果按照需求, 這個程式本身原本就應該能處理 null 值的, 那麼你是不是應該把參數改成 (int? a, int? b) 呢?

接著, 整數被除過之後, 應該回傳的是 float 型別, 但它卻是 int; 那麼, 它到底是應該取整數部份, 還是取四捨五入, 還是應該把回傳型別改成 float? 還是其它?

像這種問題, 還不用等到真正測試時; 當測試人員在寫測試程式時就應該發現了。

受過訓練的測試人員會比單純的開發人員更專注於發掘問題。如果一個簡單的整數相除函式都能衍生出其它問題, 那麼更複雜的商業邏輯還能交由設計者自己測試嗎? 根據我自己的經驗, 我認為光是設計單元測試的 test data 就已經是一個繁雜到無已復加的麻煩事了, 還不用談到什麼 performace/load test、boundry test 等等。如果連單元測試都交給開發者自己來寫, 那會是如何的繁重負擔? 況且, 搞測試的人都知道一個常識, 那就是千萬不要讓開發者自己測試 (或者說是不要相信他的測試結果), 因為對一個程式來講, 盲點最大的就是開發者自己。因此, 由旁人來做測試才是相對保險的做法。


Dev 2Share @ 點部落