l

2023年6月20日 星期二

你就是寫太多測試才會沒時間(2):自動化測試是金字塔嗎?

June 20 09:55~11:46

  


▲圖1:傳統的自動化測試形成金字塔形狀,單元測試占比最大


前言

傳統軟體自動化測試形成如圖1的金字塔形狀,作為驗證個別軟體元件正確性的單元測試數量做多,整合測試次之,透過使用者介面驗證系統功能的使用者驗收測試或稱為End-To-End測試數量最少。

多年來,很多撰寫自動化測試的開發人員心中大致依循著測試金字塔去規劃與撰寫他們的測試案例。但是,雖著單元測試越來越多,系統功能不斷地演化以及持續重構改善設計,開發人員經常會發現:「靠,剛剛的修改造成N個單元測試失敗。更慘的是,我看不懂這些失敗的單元測試為什麼失敗。」

整合測試因為以黑箱的角度測試「系統功能」,因此相對而言對於軟體修改與重構的抵抗力比較高。但光使用整合測試驗證系統品質存在兩個問題:

  1. 發生錯誤時不易除錯(不容易明確看出錯誤發生在哪個地方)
  2. 執行速度比單元測試要慢很多,導致回饋路徑較長,降低開發人員持續測試的意願

如果可以減少單元測試的數量同時維持單元測試的效果(避免上述兩個問題),自動測試有沒有可能從金字塔變成如圖2所示的菱形


▲圖2:自動化測試有沒有可能是菱形?

 

今天這一集先談第一個問題,下一集再談測試執行速度的問題。

***

用合約取代單元測試

測試只是一種驗證系統行為的方法,在軟體工程中除了測試以外還有一種也算是廣為人知但較少人做的方法:合約式設計(Design by Contract;DBC)也可以規範系統行為。DBC的作法很簡單,模仿真實世界人類的合約,幫軟體元件撰寫合約。軟體合約主要包含:pre-conditions(前置條件)post-conditions(後置條件)以及class invariants(類別不變量)。為了簡化起見之後的範例只討論前兩者,先忽略class invariants。

圖3是ezKanban系統中的Workflow aggregate單元測試,這也是傳統用來驗證程式行為的做法。


▲圖3:Workflow單元測試

 

圖4是幫Workflow的建構函數撰寫合約的程式範例,其中第42~44是pre-conditions,第48~61是post-conditions,第46行是method body。當程式執行的時候,只要pre-conditions和post-conditions都通過,那麼不管method body如何實作,它的行為就被視為具備正確性(correctness)


   ▲圖4:幫Workflow寫合約 

***

看到圖4的範例,鄉民們可能會覺得:「類別的合約就是幫method做輸入參數檢查,然後把平時寫在單元測試裡面的assertions移到production code裡面而已啊。」這樣做雖然不用寫單元測試,但是這些合約也是程式碼,也是要花時間撰寫與維護,這樣有省到時間嗎?

這個問題可以從幾個方面來討論:

  • 不用寫arrange和act:撰寫單元測試有三個步驟,arrangeactassert。寫成合約之後,assert部分還是存在,但少了arrange與act。寫過自動化測試的鄉民們應該很有感,很多時候花在arrange的時間甚至比assert還多。減少arrange與act除了少掉撰寫的時間,也避免了之後需求變更或軟體重構導致需要維護單元測試的時間。
  • 和Production Code生活在一起有助於開發與維護:寫在production code裡面的合約(post-conditions),看起來跟寫在測試裡面的assertions很像,但合約並不是把測試寫在production code,而是把規格寫在production code,這兩者有很大的差別。將規格寫在production code,當規格改變之後,可以直接修改production code(反之亦然),減少context switching。
  • Caller和Callee責任清楚:撰寫合約也可以釐清物件之間的責任,Caller需要滿足pre-conditions,Callee需要滿足post-conditions。當合約被違反時,丟出的例外訊息可以協助找出錯誤,這一點可以達到和單元測試一樣,甚至更好的除錯效果。

***

誰來驗證合約?

看到這裡鄉民們應該有一個疑問:「我可以直接執行單元測試,寫在production code裡面的合約要怎麼執行?」另外,「合約也是程式碼,寫錯了怎麼辦?要不要寫測試來驗證合約?」

合約是在系統執行期間(runtime)被執行與驗證,所以還是要有「人」來執行這些合約。這就是圖2中的整合測試所要負擔的責任:透過整合測試來執行合約

有沒有需要另外寫其他類型的測試來驗證合約?基本上不需要,當執行驗收測試時如果合約失敗,和單元測試執行失敗一樣,可能有兩個原因:production code寫錯或測試寫錯(合約寫錯),然後開發人員就必須介入排除錯誤發生的原因。

***

結論

透過驗收測試驅動合約,可以極大幅度減少單元測試的數量,接下來只要可以加速驗收測試執行速度,實務上就有可能落實Teddy所介紹的這套方法。至於如何加速驗收測試執行,這個問題比較複雜,下集再談。

***

友藏內心獨白:「你就是寫太多測試才會沒時間」都是真的 XD。

沒有留言:

張貼留言