無瑕的程式碼 (9):單元測試

Whyayen
嗨,世界
Published in
5 min readSep 9, 2020
Photo by Ferenc Almasi on Unsplash

前言

撰寫單元測試能確保程式裡的每個角落都會如同預期般地運作,然而一股腦將測試加入程式碼內,而忽略了測試的可讀、可維護性,將會阻礙後續測試新增、修改,久而久之這些阻礙將使得測試變得愈來愈混亂,也漸漸的失去測試的意義。

TDD 的三大法則

  1. 在撰寫一個單元測試(測試失敗的單元測試)前,不可撰寫任何產品程式
  2. 只撰寫剛好無法通過的單元測試,不能編譯也算無法通過
  3. 只撰寫剛好能通過當前測試失敗的產品程式

這三條法則使你被限制在一個約 30 秒的循環內。測試程式和產品程式是一起被撰寫的:測試程式只比產品程式早幾秒撰寫而已。

讓測試程式整潔

某些團隊允許測試程式不應該以產品程式的維護標準來進行維護。他們允許在寫單元測試時,可以打破維護原則,「快速和醜陋」是他們的口號。

醜陋的測試程式,等同於沒有測試程式,當這些測試程式隨著產品程式的演進而修改,則會越來越混亂及難修改,當測試程式陷入一團混亂時,就需要花費越多的實際,將新的測試程式塞進這套測試單元時,舊的測試程式就開始失敗,隨著版本的迭代,測試會更難維護。

測試程式跟產品一樣重要,測試程式不是次等公民,它需要花時間思考、設計和照料。

[備註]當然,在提交出去之前程式碼的快速和醜陋是沒什麼關係的。TDD 的快速循環初始程式碼可能會很醜,但到了功能完成並提交的時候應該是要符合團隊的品質標準。

測試帶來更多的好處

單元測試讓我們的程式保持

  1. 擴充性
  2. 可維護性
  3. 可再利用性

如果有了測試程式,就不會害怕修改原本的程式,即使面對一個不盡如人意的架構和晦澀的設計,你也可以進行無後顧之憂的修改。擁有涵蓋產品程式的一套自動化單元測試,是讓你盡可能的維護產品設計和架構整潔的唯一關鍵,因為測試讓修改變為可行。

因此,如果測試程式是醜陋的,就會限制住修改程式碼的能力,並且會開始失去了改善程式碼架構的能力。測試越醜陋,程式碼會變得醜陋,最終失去了測試,程式碼也開始腐敗了。

整潔的測試

可讀性造就了一個整潔的測試,可讀性在單元測試裡可能比在產品程式裡重要。是什麼讓一個測試程式具有可讀性?

  • 闡明性
  • 簡明性
  • 言簡意賅的表達力

每個測試應該很清楚地被拆解成三個部分:

  1. 建立測試資料
  2. 操作這些測試資料
  3. 檢查「操作這些測試資料以後,是否產生預期的結果」

特定領域的測試語言

書中 Listing 9-2 建立了一連串的函式和公用程式,來間接使用這些 API,讓我們的測試程式更容易撰寫和閱讀。這些測試 API 並不是一開始就特別設計,是從被困惑的測試程式進行持續重構的過程中,簡化而來。

一個測試一個斷言 (Assert)

有些學派的思維,認為每個測試函式只能有唯一一個斷言敘述。這樣的準則似乎過於嚴厲,但好處是這些測試只會產生一個結論,可以藉此容易且快速了解測試內容及結果。大原則是測試裡的斷言數量應盡可能減少。

一個測試一個概念

也許更好的準則是:在每個測試函式裡只測試一個概念。多重斷言可能檢查了多件獨立的事情,會強迫讀者得找出每個測試在這裡的理由,還有每個測試區塊要測試什麼,所以最好的法則應該是:在一個概念裡最小化斷言的數量,一個測試函式只測試一個概念。

一個關注點是一個工作單元的一個最終結果(回傳值、系統狀態),如果你的單元測試對多個物件進行了驗證,或是測試了一個物件是否回傳正確的值,又驗證了系統狀態改變導致這個物件的行為發生變化,那麼這個測試可能一次在測試多個關注點。

單元測試的藝術 — 8.1.3 每次只測試一個關注點

F.I.R.S.T

整潔的測試應遵循下列五個字母縮寫所構成的法則:

  • Fast(快速):測試應該要夠快。 測試應該要能被快速地運行,當測試緩慢時,你就不會想常常執行他們,不常執行就無法及早發現問題,也無法輕易地修正
  • Independent(獨立):測試程式不應相互依賴。 一個測試不應成為下一個測試的設定條件,要能獨立運行各個測試,並且可以按照任何想要的順序進行測試,當測試相互依賴,第一個測試的失敗會導致一連串接續的失敗,讓錯誤的診斷變的困難
[討論]E2E 測試很容易遇上相依性的問題,比如說需要測試的項目包含了『建立職缺』、『確認職缺正確』、『修改職缺』,沒有特別獨立三個測試的話就會變成只要有一步失敗可能就會跟著失敗。解決方法:為每個測試 Setup 不同的資料,如:建立職缺、讀取職缺與修改職缺都是對應 不同的測試資料,改完確認沒問題後,在測試完後把資料改回去
  • Repeatable(可重複性):測試程式應該可以在任何環境中重複執行。 要能夠在產品環境中進行測試,在 QA 環境中進行測試。如果測試無法在任何環境下重複進行,就永遠會有「為什麼測試會失敗」的藉口。
  • Self-Validation(自我驗證):測試程式應該輸出布林值。 不管是測試通過或失敗,不應該透過查看紀錄檔來分辨測試是否通過,不應該手動比較兩個不同的文字檔才能得知是否通過。
  • Timely(及時):撰寫測試程式要及時。 單元測試要恰好在使其通過產品程式之前不久被撰寫。如果在撰寫產品程式之後才撰寫測試,那可能會發現產品程式很難被測試。

結論

把測試當成產品程式碼一樣重視,並遵循上述幾點,能使我們在新增、修改及後續維護上方便許多,能在各個環境下執行測試,並快速地運行,產生測試報告,使我們沒有失敗的理由而放棄撰寫、執行測試,讓產品程式碼更穩健。

--

--