(譯)TDD(測試驅動開發)的5個步驟
原文:5 steps of test-driven development //developer.ibm.com/articles/5-steps-of-test-driven-development/
作者 Grant Steinfeld
發表於 2020年2月6日
在這篇文章中,我將向你介紹TDD的基礎概念,如果你是敏捷開發實踐者,那麼TDD將是你開發生涯中的最佳實踐。學習TDD是什麼,理解TDD的基本流程並且知道怎麼用單元測試實現TDD。你將會理解為什麼在開發過程中需要使用TDD。
什麼是TDD
TDD顛覆了傳統的開發和測試。TDD不是先寫業務代碼,而是先寫測試代碼,寫業務代碼來反向滿足測試代碼的校驗。TDD規定你先寫測試代碼,然後實現代碼直到剛剛寫的測試代碼通過。
在TDD中,你先寫測試,觀察其失敗,然後實現代碼直到測試通過,聽起來很倒退是吧?但是,當你使用這種測試方法時,你生成的代碼會更整潔,從長遠來看更不容易出錯。
一個單元測試要是簡單的,只涵蓋一小部分邏輯的測試,例如算法。單元測試應該是確定性的。這裡的「確定性」的意思是單元測試不應該有副作用,比如調用提供隨機或變化數據的外部 API。同理,你將使用模擬數據代替可能隨時間變化的數據。
TDD的五個步驟
TDD流程中有以下五個步驟:
- 閱讀、理解和處理功能或錯誤請求。
- 通過編寫單元測試來實現需求。如果你設置了熱重載,因為尚未實現任何代碼,所以此時單元測試是失敗的。
- 編寫並實現滿足要求的代碼。運行所有測試,它們應該通過,如果沒有通過則重複此步驟。
- 通過重構來整理你的代碼。
- 重複。
下圖展示了這些步驟及其敏捷、循環和迭代特徵:
該工作流程有時也被稱為紅-綠-重構 (Red-Green-Refactoring),這個稱呼來自周期內測試的狀態。
- 紅色階段表示代碼不起作用。
- 綠色階段表示代碼都能正常工作,但不是以最佳的方式進行。
- 藍色階段表示開發人員正在重構代碼,但他們確信代碼已被測試覆蓋,這使開發人員有信心修改和改進代碼。
測試驅動開發和持續集成/持續交付
(CI) 是一種開發實踐,需要開發人員每天多次將代碼集成到共享存儲庫中。然後通過自動構建驗證每次嵌入,從而使團隊能夠及早發現問題。通過定期集成,你可以快速發現錯誤,並更輕鬆地定位它們。
TDD 產生的單元測試也是持續集成/持續交付 (CI/CD) 過程中不可或缺的一部分。
TDD 的單元測試和持續集成/持續交付管道,如 CircleCI、GoCD 或 Travis CI,它們在提交時運行所有單元測試。
測試在部署管道中運行。如果所有測試都通過,就會進行集成和部署。但是,如果任何測試失敗,該過程就會停止,從而確保構建不會被破壞。
首先設置你的工具、工具鏈和IDE
為了進行測試驅動的開發,你需要先設置你的工具、工具鏈和 IDE。在我們的項目 [code pattern] 中,我們正在開發一個 Node.js 示例,這裡是我們設置的關鍵工具:
- 用於 Node.js 和 NPM 的 nvm(Node版本管理器):NVM 允許你運行所需的 Node.js 版本並對其進行更改,而不會影響系統node。
- 用於開發的 npm 庫:
- Jest 用於單元測試
- ESLint用於 linting
- Prettier用于格式化
- Husky 和 lint-staged 用於預提交 Git hooks
如何編寫失敗的單元測試
以下是幾種不同的方法來編寫失敗的單元測試。
-
編寫一個測試,引用代碼中尚不存在的函數,這將導致測試失敗並出現未找到的錯誤(例如,404 錯誤)。
-
更改斷言(assert)語句以使其失敗。斷言語句表示被測試的代碼預期返回什麼值;這種語句是單元測試的關鍵。斷言語句應反映功能或錯誤請求的響應。
因此,要使單元測試失敗,你需要編寫一個斷言語句,該斷言會在你想要豐富的數據結構中返回一個暫時沒有的值。例如,你的 JSON 返回一個人的姓名,但你的新需求需要包含該人的手機號碼。你將首先編寫斷言語句來包含該人的手機號碼,這將導致它失敗。然後,你將添加業務代碼來增加該人的電話號碼。
或者,在現實的編碼中:你的斷言語句可能是:assert actualResult == {‘track’:’foo fighters’}。一旦生產代碼(函數)被創建,編譯錯誤被解決,404 就會消失,但實際 actualResult 返回的結果可能是像 {} 這樣的空對象。接着再將函數中的返回結果硬編碼為 {『track』:『foo fighters』}。
測試現在將通過(綠色!)。代碼現在顯然只是臨時的,但你可以得到基本的理解。測試已正確連接到生產代碼中的某個點。從那裡你可以實現實際的業務邏輯,例如,讀取文件/db/調用外部 API
決定何時編寫單元測試
一般來說,編寫單元測試有兩種情況:
案例 A:你為代表簡明故事的請求編寫單元測試。例如,請求可能是計算特定貨幣兌換所支持的國家/地區數量。我做的第一件事是編寫一個單元測試並看到它失敗。然後,我迭代地更改代碼,直到單元測試通過。
案例 B:生產中發現的一段錯誤代碼。此錯誤觸發的問題需要實施修復/補丁。回到貨幣兌換示例,代碼運行時,用戶期望在許多國家/地區使用 $USD,但該行為是錯誤的,目前只有一個國家/地區返回。
我做的第一件事是編寫一個單元測試並看到它失敗。然後,更正我的實現代碼,直到測試通過。這不僅修復了代碼並消除了錯誤,而且還為我提供了一個可以重複使用的單元測試,以確保這段代碼保持完整。
總結
大多數程序員不使用測試驅動開發來編寫代碼,但他們應該這樣做。測試驅動的開發可以創建更好的容錯性代碼。希望你從這篇博文中了解 TDD 的理念,並將其融入你的軟件開發實踐中。
下一步
請繼續關注有關如何在 Node.js、Java 和 Python 中進行測試驅動開發的新博客文章。
資源
非常有幫助的書與文章
Using Test-Driven Development for Microservices, Bill Doerrfeld
Test-driven Java development: Invoke TDD principles for end-to-end application developmnet, 2nd Edition by Farcic, Viktor
Unit testing principles, practices, and patterns, Vladimir Khorikov