TDD之為何要Test First ?

Z-xuan Hong
5 min readSep 8, 2020

--

前言:嘗試使用TDD來開發軟體已經有3年時間,此篇文章記錄實踐過程中所獲得的感想,每個人想法有所不同皆屬正常,歡迎下面討論。

此篇文章希望達到下列目標:

  1. 了解為什麼要TDD
  2. 了解為什麼TDD這麼難
  3. 了解何時不需使用
  4. 透過範例解釋,讓大家能夠實際應用

為何要Test First? 在需求還沒實作完的情況下為什麼要先寫測試,這樣是否有點違反人性?相信對於第一次接觸TDD的人都有這種想法,到底為什麼要Test First呢?

  1. 解決的問題:開發時,我們會先進行設計並且實作,但在設計到實作的過程中通常沒有任何回饋能夠告訴我們設計的好不好,較有經驗的工程師能夠透過OO等設計原則去判斷並且加以修改,但是在只關注Solution Space(Production Code)的情況下,容易造成過度設計(因為不知道具體目標),甚至忽略沒注意到(難以發現Class違反SRP),因此我們不能只關注Solution Space,TDD的方式能夠讓我們關注到Problem Space,解決的設計回饋不足的問題。
  2. 優點:TDD給我們設計上大量的回饋,而且此回饋的頻率很高,十幾秒就能獲得回饋,讓我們更容易發現設計不良並且修正,並且根據目前程式的回饋進行修正,不會造成過度設計,讓我們更關注要解決的問題,商業邏輯也會以測試的方式呈現,當成Document給開發人員參考,也讓開發人員透過測試得知物件API的使用方式、物件要解決的問題。

TDD為什麼這麼難?

  1. 需要具備“看人看不到的事,聽人聽不到的聲音”的能力,我們知道TDD能夠給予我們設計上大量的回饋,但是初學者通常很難發覺TDD給予的回饋,更不用說能夠針對此回饋提出相對應的解決辦法,導致學習曲線過長。
  2. 通常能夠上手TDD的開發者,也會需要具備OOAD、SOLID、Design Pattern、Refactoring等能力,底子要求較高,
  3. 因此推薦大家能夠試試看TDD,並且耐心察覺他給予我們的回饋、學習如何針對此回饋提出改善。儘管一開始非常困難,但在解決TDD給予的難題時,同時也能慢慢加強軟體設計的能力(傑克,有這麼神奇?)。

何時不需使用?

  1. 對於API如何使用完全沒概念,例如: 新工具API完全沒有接觸過
  2. 難以自動化,例如: UI端
  3. Prototype(雖然有繼續維護的可能)
  4. 大部份時候都應該試著用用看,可能會有意想不到的收穫

範例

  1. Test Coupling:當測試彼此之間有耦合時,代表程式碼使用到Global、Static、External Dependency等概念,會”大量”使用Global、Static代表系統沒有做好適當的封裝、耦合到External Dependency則違反DIP,讓實作細節變動影響高階邏輯。
  2. Large Test Class:當測試感覺寫不完時,通常代表違反SRP,使用重構的Split Class分開兩種責任,針對分開的責任個別進行測試,能夠讓程式碼之間的耦合性更低,達到Loose Couple。
  3. Multiple Acts:由於Test是以Problem Space的觀點去看我們的設計,當物件需要呼叫多個Message來達到客戶端目標時(Observables Goal),通常代表物件沒有確實封裝,導致測試耦合到物件的實作細節,應把多個Message封裝成一個Message,並且此Message應該以客戶要達成的目標(Goal, Not How)進行命名,能提升物件的封裝程度。
  4. Structure Duplication in Test: 當Test裡面出現重複結構時,代表缺乏適當的抽象化,應該加入新的物件封裝重複的結構,以增加系統封裝程度。
  5. Refactoring Application Results Failure Test: 當修改程式而且不改變行為的情況下,測試卻亮紅燈,代表測試與物件的耦合性過高,大量使用Mock也會導致此結果(Over Specification Smell),讓測試無法抓出行為異常,而是亂抓異常,導致開發人員對測試沒信心不理會他,造成當真的因為行為異常出現紅燈時,也被開發人員忽略,降低測試的價值(沒辦法有效的抓出Regression Error)。
  6. Large Object Graph:當要測試一個物件A時,需要此A的Dependency: B, C, D ,而生成B, C, D時,需要B, C, D個別的Dependency,導致測試難以撰寫,有人提出使用Mock的方式來解耦合,但還是不能改變此物件責任過大,違反SRP的根本問題(Root Cause),應該要使用Refactoring的Split Class,針對A物件進行切割,讓整體設計Cohension上升,避免寫出God Class(物件導向的優點為透過多個High Cohesion的物件去達成需求目標,降低修改時對於系統的影響,God Class則像是傳統的C語言,透過Global Control去完成功能,當底層元件修改時,會影響上層系統導致難以維護)

結論: 有時不是TDD違反人性,而是我們設計能力不足無法感受到TDD給我們的設計回饋,並且透過此回饋改善我們的設計。需要具備“看人看不到的事,聽人聽不到的聲音”的能力

--

--

Z-xuan Hong
Z-xuan Hong

Written by Z-xuan Hong

熱愛軟體開發、物件導向、自動化測試、框架設計,擅長透過軟工幫助企業達到成本最小化、價值最大化。單元測試企業內訓、導入請私訊信箱:t107598042@ntut.org.tw。

No responses yet