測試之要寫還是不寫?這是個好問題!

Z-xuan Hong
5 min readApr 29, 2022

--

參考書籍

前提

最近在公司專案看到一個有趣的案例:由於一開始把簡單的驗證邏輯寫在sp,導致只寫單元測試是不夠的 (只能使用mock方式),所以同事事後補了一組整合測試。

在進入主題之前,我想先說:同事寫的整合測試可讀性很高、算是品質很好的測試,文中沒有任何批判,單純分享個人想法。

先說結論:這組整合測試可讀性高、品質好,但我認為價值趨近於0,跟上面使用mock的單元測試一樣,價值趨近於0,因為從一開始就沒有解決掉根本原因。

在解釋為什麼之前,我們必須先從一個判斷測試價值的體系談起 (不是我發明的,有興趣深入了解的人請參考書籍reference)。

這個體系由三個元素組成:目標、原則、實務考量

  • 目標:測試需要為開發團隊帶來穩定、持續開發的目標。
  • 原則:測試要能無瑕的整合進開發過程、測試只鎖定在重要的區域、測試要提供最大價值最小成本。
  • 實務考量:測試要能catch regression error、測試要經的起重構的考驗、測試要有合理的速度、測試要好維護。

接下來我會用上面的指標去分析為什麼上述那些測試的價值趨近於0。

接下來上面提到的整合測試稱之為BIT (bad integration test)、單元測試稱之為BUT (bad unit test)。

實務考量 for BIT:

- catch regression error:為了確保catch regression error,測試要執行越多程式碼越好,但透過整合測試測試1~5行的sp,與目標相差太大。因此,BIT對這個指標的分數我只給 (0.3/1)。

- refactor:測試要能夠讓開發者輕易重構,這也是整合測試的優點,客戶端只專注行為,不是細節。因此,BIT對這個指標的分數我給 (1/1)。

- speed:整合測試顧名思義會耦合到外部環境,像是連接資料庫等等,造成速度過慢,因此,BIT對這個指標的分數我給 (0.3/1)。

- maintenance:維護性好的測試有著程式碼少、環境好準備等特性,但整合測試不符合上述標準。因此,BIT對這個指標的分數我給 (0.5/1)。

判斷分數只要把上述概略的評分相乘即可,所以只要有任一個指標太低,都會造成分數的驟降,這是故意的,目的是為了確保測試能維持各個指標的平衡。

s = 0.3 * 0.3 * 1 * 0.5 = 0.045

原則 for BIT:

- 無瑕的整合進開發過程:這點大家都清楚,整合測試很難有效整合到每分每秒的開發過程。

- 鎖定在重要的區域:上述邏輯是領域邏輯,所以有滿足鎖定重要的區域這個要求。

- 最大價值最小成本:同事花了成本2~3小時撰寫BIT,獲得分數為0.09,沒辦法滿足此要求。

目標 for BIT:

- 當測試suite中包含太多價值過低的測試,無法有效為開發團隊帶來穩定、持續開發的目標。

上述就是為什麼BIT我認為價值為0的原因。

***

實務考量 for BUT:

- catch regression error:BUT透過mock把呼叫sp的依賴隔離掉,根本測試不到任何邏輯。因此,BUT對這個指標的分數我給 (0/1)。

- refactor:測試要能夠讓開發者輕易重構,但過度mock造成over specification code smell。因此,BUT對這個指標的分數我給 (0/1)。

- speed:不管有沒有mock,BUT速度都很快。因此,BUT對這個指標的分數我給 (1/1)。

- maintenance:BUT在沒有驗證任何邏輯的情況下,還有撰寫一堆mock設定。因此,BUT對這個指標的分數我給 (0/1)。

s = 0 * 0 * 1 * 0 = 0

原則 for BUT:

- 無瑕的整合進開發過程:由於BUT速度很快,要整合到每分每秒是沒有任何問題。

- 鎖定在重要的區域:BUT透過mock將邏輯限制在object之間的interaction,沒有達到鎖定重要區域的目標。

- 最大價值最小成本:撰寫耗時,得到的分數過低 (0),因此沒辦法達到最大價值與最小成本的要求。

目標 for BUT:

- 當測試suite中包含太多價值過低的測試,無法有效為開發團隊帶來穩定、持續開發的目標。

***

看到上面,希望大家能理解為什麼我會說上述的測試價值趨近於0。

導致撰寫BIT、BUT的根本原因為:

  • 將領域邏輯放在sp中,無法輕易對domain object進行單元測試。
  • domain object or service只能呼叫抽象sp的介面,造成外部依賴過多,只能被迫撰寫mock test。

上述兩點導致我們,使用殺雞焉用牛刀。(BIT for very smell logic、BUT for trivial interaction code with mock)

那怎麼樣才是好的單元測試 (GUT)、整合測試 (GIT)?

  • GUT:沒有任何外部依賴 (mock),只測試in-memory pure complex logic。
  • GIT:執行越多程式越好,盡量cover happy path整條flow,提高catch regression error的能力。

為了達到上述目標,就必須

  • 嚴格滿足domain model isolation的要求,讓物件external dependency越少越好
  • 將application service套用humble object,讓其負責domain object與external dependency的互動。

如此一來我們就能針對domain model撰寫GUT、針對application service撰寫GIT。

但上述兩個precondition對於data driven design的系統是沒戲唱的,這也是為什麼大部分公司導入單元測試都失敗的根本原因。

對於data driven design系統,會建議撰寫GIT而不是追求虛無縹緲的GUT (因為從設計思維上就100趴做不到)。

結論:測試也是負債,不是越多越好,沒辦法分清楚目標、原則、實踐很難寫出價值的測試。

--

--

Z-xuan Hong

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