【單元測試】的四面向 by pcbill | CodeData
top

【單元測試】的四面向

分享:

【單元測試】改變了我程式設計的思維方式 這篇文章公開發表並貼到 Facebook 上的一些技術社團後,引發了不少討論。會有這麼熱烈的討論,我認為因為在各面向,大家對單元測試的了解程度情況不一,實行單元測試後產生不同的個人體驗結果,因此對單元測試有了不同的見解。所以就有人見識到單元測試的美好國度,也有人覺得沒有什麼,懷疑它到底有沒有效益?

【單元測試】改變了我程式設計的思維方式 提到,單元測試只是一個程式開發的方法論之一,不管有沒有採用都可以開發出你的軟體程式。姑且不論有哪些效益,這網路上有太多討論了。我想從另一個角度,切入採用單元測試後,所產生的四種面向。對於種種的討論,正反辯證前,應該要先分辨是處於哪個面向裡,不同面向,討論著眼點應該會不一樣,不然討論容易失焦,牛頭不對馬嘴。

我觀察到的四面向有兩個變因
1) 是先寫目標程式?還是先寫單元測試程式?
2) 兩種程式是同一人撰寫?還是由不同人來撰寫?

所以可以畫圖如下表示

             先寫單元測試程式
                   |
               B   |   A
   同一人撰寫--------+-------- 不同人撰寫
               D   |   C
                   |
               先寫目標程式

我們就來逐項檢視,看看有甚麼不同。

【D 面向】都是由同一人撰寫,先寫目標程式,後寫單元測試

大部分關於單元測試的討論,都是落於這個面向。也就是說我已經寫好了目標程式後,然後去"補寫"單元測試。這情境會發生的原因不外乎聽過單元測試種種好處的開發者,嘗試想寫寫看。或是為了想通過外包專案的檢核,規定要有單元測試報告,及涵蓋率門檻的開發者。

討論串理還有人提出一個看法,發現臭蟲時,會傾向要寫出一個測試的條件去証明那臭蟲已被抓到,這會花多一點時間,但是是值得的,可不想日後被人質問:「為何解決的 bug 又會再出現?」

除此之外,更積極的目的是要重構程式。針對要重構的目標程式碼,在動手之前先寫下相關的單元測試,先確定修改之前的程式行為為何? 然後執行重構後,再一次執行單元測試。PASS 就是沒有改壞,可以快速確認此單元測試的涵蓋範圍,重構後沒有引入新的 bug 。

這是正常直覺發生的面向。不過會有一個盲點要小心,不要落入陷阱,就是因為兩者都是同一開發人員撰寫,開發人員其實對於目標程式的實做細節應該是非常熟稔,所以在寫測試時,很有可能會鑽牛角尖的測試這些細節,變成為了測試而寫測試,盲目追高涵蓋率的情況。並不會去思考這樣設計是否正確合理。

【C 面向】某甲先寫目標程式,某乙後寫單元測試

這情況常常發生於某乙需要維護某甲因為某些因素無法繼續維護而留下來的專案。不過其實會這樣實踐的人不多。很多情況就是在還沒完全搞懂商業邏輯的情況,需求又一直進來,只好硬著頭皮直接改目標程式。這樣作風險很高,建議依靠多寫些單元測試來打保險,修改完目標程式就可以馬上執行單元測試驗證是否正確。也就是執行 D 面向所提及的積極作為。

除此之外,還有一種情況就是單元測試可以用來"學習"第三方函式庫,把撰寫單元測試作為學習探索的工具,而不僅僅只是測試的工具。正如 Clean Code 第八章所描述。【單元測試】改變了我程式設計的思維方式 也有提及,可以參考一下。

【B 面向】都是由同一人撰寫,先寫單元測試,後寫目標程式

這就是所謂的 TDD(Test-Driven Development),由 Kent Beck 所提出。

然而有一派人認為測試一詞很容易讓人誤解,以為是要測試什麼已經存在的程式,也只有已存在的程式才能夠被測試。不過 TDD 的作法是目標程式依據單元測試案例產生出來,是依照單元測試定義的規格產出,哪來需要什麼測試?於是就有 Dan North 就提出了 BDD(Behavior-Driven Development) 這一概念,說明撰寫的單元測試不是用來測試目標程式,而是透過單元測試的形式,制定目標程式的行為規格,依照此規格,花最小的力氣,寫出需要的目標程式。

所以強調的是實作前的規格制定,可以視為將規格文件以程式寫成,而單元測試只是這些規格的描述形式。

所以一個有趣的發現是有 BDD 宣稱的一個 Javascript 測試 framework Jasmine 沒有名為 test 的相關資料夾,但有名為 spec (意指specification) 資料夾,但是理面放的其實就是 behavior 形式的單元測試程式。

撰寫本文時,有幸拜讀同人的 不要把 TDD 和做測試混為一談 此篇文章,對於 TDD 的闡述已經非常的清楚明瞭,搭配業界實際的例子,解開大眾對 TDD 的誤解,精闢入理,值得細細品嘗詳讀一番。我摘要一些內容如下:

TDD 的目的是要確認花費最小的努力來滿足使用者的需求,測試案例所要求的不是全面的完整,而只是確認達到剛好達到滿足需求的邊界,這將會促使開發者先以界面的思維來整合系統各要件。對 TDD 測試案例比較重要的問題,是如何設計可測元件與非可測模組之間的界面。

如果開發者熟悉 TDD 的節奏,當他沒辦法寫出測試程式的時候,代表他對系統所要解決的具體問題還不夠清楚。為了要弄清楚他必須更清楚去溝通,而且 TDD 的習慣會促使他傾向以使用者的情境去溝通,而不是拿功能要使用者告訴他該做什麼。只有在開發者瞭解使用者的情境之後,開發者才能具體而正確地寫出驗證程式的測試案例,這說明在 TDD 寫的測試案例不是做測試而是在做設計。

TDD 的成功並不在 TDD 自動確認「程式的正確性」,而在於TDD 讓人有勇氣把事情做好,讓人遵循良好解決問題的節奏和紀律。那就是:1.確認問題;2.依照問題情境,發展用來驗證方案的標準;3.設計方案並執行驗證,以達符合驗證標準;4.視組織需要改善方案,再次執行驗證以確認符合標準。

把做測試和 TDD 混為一談的觀點,忽略了 TDD 的價值不在檢驗程式的正確性,而是找出足以解決問題的系統邊界的設計過程、以及面對目標採用有效的節奏把事情做好。對於 TDD 而言,測試不是用來確認程式沒有錯誤的目的,而是定義系統邊界的手段與過程,因為品質的關鍵不在於確認沒有錯誤的程式,而是從程式接口之間發現軟體自然演化的脈絡

我實在想全文收錄,不過實在太多了,還是請大家點連結閱覽吧。

討論串裡也有討論到,不要成為 TDD/BDD 的基本教義派。我覺得這和"物件導向語言是否是程式設計的銀彈"是等價的問題,況且我們周遭很少有人推 TDD/BDD,不是嗎?有這樣的 issue 出現,通常就是這東西很熱門,開始流行的時刻。

另外,我也戲稱 TDD/BDD 為"許願式程式設計",【單元測試】改變了我程式設計的思維方式 亦有闡述。

【A 面向】某甲先寫單元測試,某乙才針對某甲的單元測試撰寫目標程式

這面向非常的不直覺,我也罕有聽過有這樣的案例以及討論。我認為這面向是進階的 TDD/BDD ,某甲寫單元測試當作系統規格,然後交由某乙依據此規格,撰寫目標程式,實作出規格。

這很適合應用在外包專案的情況。某甲要外包某專案,所提出的規格可以不再是文件呈現,而是以單元測試的形式。這樣可以減少兩方的溝通落差,日後驗收也可以直接以這個標準來檢驗。不過前提是發包方某甲要對撰寫程式要有一定的程度概念,才寫得出像樣的單元測試型態的規格。不過發包方通常就是不懂程式才會外包,但又要要求對撰寫程式有概念,這很困難。不過我覺得這是很好的以單元測試來實踐專案分工的方式。

規模大的系統也比較有可能實行這面向,想想,系統如果大到開發人員需要專職的分工成 System Architect(SA)、System Designer(SD)、Programmer(PG)。SA 或 SD 就可以以單元測試為寫規格工具,撰寫單元測試,然後 PG 可以依照此規格撰寫出目標程式。可將單元測試作為設計和實作兩方共同的溝通工具,不用再花多餘的時間去撰寫,也許一直無法同步的規格文書;日後更可以作為驗收的依據,而且可用工具自動驗證。很美好,何樂而不為?

總結

所以在討論實作"單元測試"適當與否之前,可以先釐清處於哪一個面向。而上各面向實行的難易度正如各面向所命名,A > B > C > D,不管分類為何,一致的看法就是一定要動手去作,才能了解體會單元測試的好處,或是一無是處。

討論串整理

稍微整理條列一下,討論串裡大家的看法。

不去實作的理由:
- 節省開發時間,認為沒必要
- 找 bug 不是那麼有效,倒不如使用 debug 或是 code scan 方式

要實做的理由:
- 程式經常使用、程式 bug 造成損害高
- 幫助設計好架構
- 需要多人維護、 refactoring 的系統

注意事項:
- 不要不合時宜的盲目在「任何類型」的專案強行導入
- 「正確的測到該測的部份」、「寫出好的測試」,都需要學習時間以及功力。

覺得還是得有其他工具、方法才能相輔相成,產生威力,例如
- 源碼版本控制
- 持續整合 ( continuous integration )
- 重構

詳細的討論可至這裡參考。

分享:
按讚!加入 CodeData Facebook 粉絲群

相關文章

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

關於作者

任職過生醫資訊業、遊戲業、電信業。遊牧於大公司、小公司、半公家機關,所以體驗過各式各樣的開發流程,深信軟體開發應有最佳實踐可以遵循。所以正在努力的許願、追尋中。

熱門論壇文章

熱門技術文章