核心概念#

當我們問開發者為什麼寫測試時,他們會說「為了確保程式碼能用」。作者認為這是錯的。

Tip 66 - Testing Is Not About Finding Bugs(測試不是為了找 Bug)

測試的主要好處發生在你思考和撰寫測試的時候,而不是在你運行測試的時候。

思考測試#

想像你要寫一個函式查詢資料庫,回傳每週觀看超過 10 個影片的使用者列表。在動手寫程式碼之前,先想想如何測試它:

  1. 你需要測試資料,意味著可能需要使用你能控制的資料庫——所以應該把資料庫實例作為參數傳入函式,而非使用全域的
  2. 你需要知道用哪個欄位來判斷——所以把欄位名稱也作為參數傳入

還沒寫一行程式碼,光是思考測試,就已經做了兩個發現並改變了 API 設計。

Tip 67 - A Test Is the First User of Your Code(測試是你程式碼的第一個使用者)

測試驅動編碼#

思考測試讓我們從外部看自己的方法——彷彿我們是程式碼的客戶而非作者:

  • 緊密耦合其他程式碼的函式很難測試,因為你需要設置所有環境才能運行。所以讓程式碼可測試也降低了耦合度
  • 在測試之前你必須理解程式碼。如果你在開始編碼前思考邊界條件和錯誤處理,就能找到簡化邏輯的模式

測試驅動開發(TDD)#

TDD 的基本循環:

  1. 決定要添加的一小塊功能
  2. 寫一個測試,一旦該功能實作就會通過
  3. 運行所有測試,驗證唯一失敗的是你剛寫的那個
  4. 寫最少量的程式碼讓測試通過
  5. 重構程式碼

作者認為 TDD 對剛開始寫測試的人很有益。但也見過一些人成為 TDD 的奴隸:

  • 花大量時間確保永遠有 100% 測試覆蓋率
  • 有很多冗餘測試
  • 設計傾向由下而上,而非端到端

Tip 68 - Build End-to-End, Not Top-Down or Bottom Up(端到端建構,而非由上而下或由下而上)

務必實踐 TDD。但如果你這樣做,不要忘了時不時停下來看全局。很容易被綠色的「測試通過」訊息引誘,寫一大堆程式碼卻實際上沒有讓你更接近解決方案。TDD 需要你知道方向——否則你可能在原地打轉。

回到程式碼#

我們需要從一開始就把可測試性建入軟體,並在組合之前徹底測試每個部分——就像硬體中的 Built-In Self Test(BIST)。

單元測試#

單元測試是對每個模組單獨進行的測試。通常會建立人工環境、呼叫被測模組中的函式,然後檢查回傳結果。

針對合約測試#

把單元測試想成針對合約的測試(testing against contract,見 Topic 23)。我們要確保給定單元履行了它的合約:前置條件、後置條件、邊界值。

Tip 69 - Design to Test(為測試而設計)

Ad Hoc 測試#

手動除錯測試——console.log、在 REPL 中交互式運行。在除錯結束時,將這些 ad hoc 測試正式化,加入到現有的單元測試中。

建立測試窗口#

最好的測試集也不可能找到所有 bug。你可以提供各種方式來窺視運行中模組的內部狀態:日誌、熱鍵序列、magic URL、feature switch 等。

測試文化#

Tip 70 - Test Your Software, or Your Users Will(測試你的軟體,否則你的使用者會幫你測試)

你實際上只有幾個選擇:Test First、Test During、或 Test Never。「Test Later」通常等於「Test Never」。

測試文化意味著所有測試永遠通過。忽略一堆「永遠失敗」的測試會讓忽略所有測試變得更容易。對待測試程式碼要和產品程式碼一樣用心:保持解耦、乾淨、穩健。

測試、設計、編碼——這全都是程式開發。

相關章節#

  • Topic 27,不要跑在車燈前面
  • Topic 51,務實的入門套件