本文很多內容來自選自TDD例項一書。
預備知識
最好有一些預備知識,例如xUnit,Moq,如何編寫易於測試的程式碼,這些內容我都寫了文章:https://www.cnblogs.com/cgzl/p/9178672.html#test。
Test Driven Development
什麼是TDD(Test Driven Development)?
TDD是一個軟體開發過程,這個過程依賴於重複性的小開發週期:需求被轉化為具體的測試用例,然後改程式序以便通過測試。
在TDD裡有兩條規則:
- 只在有未通過的自動化測試的情況下,你才會去寫新的程式碼
- 消滅重複
這兩條規則在技術上的含義是:
- 你必須進行良好的設計,執行的程式碼可在決策之間提供反饋
- 開發人員得寫自己的測試
- 開發環境可以針對微小的變化需要提供快速的響應
- 您的設計必須由眾多高內聚、低耦合的元件組成,這樣測試會更簡單。
這兩條規則也意味著程式設計的三個任務:
- Red - 先寫一個不能工作/通過的小測試,甚至根本無法編譯
- Green - 快速讓這個測試通過,無論程式碼有多爛
- Refactor - 消除上個步驟中的程式碼重複。
Red,Green,Refactor,這就是TDD的咒語。
如果TDD可以很好的執行,那麼它就會大幅度減少程式碼缺陷的密度,也使工作的主題對於相關人員來說更加清晰。所以,TDD也具有社會含義:
- 如果缺陷密度可以降低到足夠的程度,那麼QA就會從被動變為主動的工作。
- 如果那些“讓人討厭的驚喜”可以減少到足夠的程度,那麼專案經理就可以精確的評估以便讓客戶參與到每日的開發工作中。
- 如果技術會議的主題足夠清晰,那麼程式設計師就會按分鐘去工作而不是按天或周來安排和進行工作。
- 如果缺陷密度可以降低到足夠的程度,那麼我們每天都可以交付出具有新功能的軟體,這就會與客戶建立新的業務關係。
這些概念都很簡單,但是動機是什麼?為什麼開發人員要去寫自動測試程式碼?為什麼開發人員在他們的思維能夠大幅飆升的設計時,卻只進行小步工作? 勇氣。
勇氣
TDD是程式設計過程中管理恐懼的一種辦法。
這個恐懼不是壞事,它是一種合理的恐懼,例如:”這個問題確實很難,我從開始的感覺看不到盡頭“。
如果疼痛是喊停的自然表達,那麼恐懼就是告訴你要“小心”。
小心是很好的,但是恐懼還有一些其它的影響:
- 讓你不得不進行更多試探性操作
- 讓你交流的更少
- 讓你羞於反饋
- 讓你脾氣暴躁
這些影響在開發的時候對你都沒有任何幫助,尤其是遇到困難問題的時候。那麼你如何面對困難處境並且:
- 取代嘗試/試探,而是儘快進行具體學習
- 取代爭吵,而是進行更清楚的溝通
- 取代避免反饋,而是尋求幫助,和具體的反饋
- 控制你自己的脾氣
TDD會管理這些事情。
為什麼要TDD
從業務角度:
- 提供了需求的確認。通過編寫測試以及RGR週期,需求確認很自然的在軟體開發的過程中就完成了。
- 捕獲迴歸問題。迴歸問題就是指隨著軟體新功能的釋出,以前的某些功能卻不好用了。TDD可以很早的發現迴歸問題。
- 綜上兩點,TDD也降低了維護成本。
從開發人員角度講,TDD還有以下好處:
- 設計為先的心態。寫測試的時候,我們就得考慮與軟體的互動應該如何實現,以便把這些功能需求程式設計可能。
- 防止過度工程。關注於如何讓測試通過和滿足客戶的期待,就會讓我們保持正軌,而不是迷失於架構設計和幻想那麼無法提供很多價值的最佳抽像設計中。
- 增加開發人員的動力。取代了花費幾天時間想盡辦法來實現某個功能這樣的操作,TDD把需求分解成一些測試,並結合RGR流程,這就允許你可以持續快速的進展並建立成功迴圈。
- 收穫自信。通過大量的測試結果,你感動支配的力量,無論修改、重構、增加功能都變得很簡單。
第一個例項
在本例中,您將會看到TDD的如下步驟:
- 快速新增一個測試
- 執行所有的測試(包括以前寫的),可以看到新新增的測試Fail了
- 修改一點程式碼
- 執行所有測試,都成功了
- 重構,移除重複
建立.NET Core 專案
這個很簡單,首先建立一個Console App:
然後再新增一個xUnit專案:
這個測試專案需要引用Console專案。
需求
有這樣一份報表:
現在想要做成支援多幣種的:
這裡還提供了匯率:
目標就是產生第二張圖那樣的報表。
開始操作
我們需要做哪些工作?
- 讓兩種幣種的錢數可以進行加法操作,並通過給定的匯率算出結果。
- 讓股票單價可以乘以股票數並得出總額。
上面是一個待辦問題列表(To-Do List)。我們就關注於這個待辦列表即可。
列表裡的問題應該是逐個解決的,解決完一個劃掉一個;如果有新問題,就在後邊加上一條。
編寫測試
下面我們開始,先不建立物件,先寫測試:
讓編譯通過
這裡有很多問題,編譯也無法通過,這些問題我們也是一個一個來解決。
1. 首先,沒有Dollar這個類,那就建立Dollar這個類:
第一個問題解決了。
2. 沒有相應的建構函式,那就建立建構函式:
又解決了一個問題!
3. 沒有Times()這個方法,那就建立該方法:
又解決了一個問題!
4. 沒有Amount屬性,建立該屬性:
編譯問題都解決了!!
看一下測試方法:
編譯錯誤肯定是沒有了。
測試Fail
然後跑測試:
不出意料肯定會Fail。
讓測試通過
現在有了具體的這個Fail的測試,我們現在的任務就是讓該測試變成Pass,而不是實現多幣種報表,先讓這個測試通過,再慢慢讓其它測試通過。
您可能不喜歡這樣,但是現在的目標不是做出完美的解決方案,目標就是讓這個測試通過,所以這時候程式碼可能很爛:
我寫死了數字10。
然後再跑測試:
測試Pass了!!
重構,移除重複
彆著急,週期還沒結束。
現在,我們需要移除重複。但是重複在哪?
通常你看到的重複是指程式碼的重複,這裡是指測試中的資料和程式碼中資料的重複。
這個10是哪來的? 它實際上是:
是通過5乘以2得來的。
所以程式碼中的5*2和測試中的5*2是重複的。 我們需要移除這個重複,但是可能需要不止一步來實現。
先把乘法移動到Times方法裡試試:
這樣的話,測試仍然會pass:
這是一小步。
那麼5是哪裡來的?
應該是從建構函式傳遞進來的,我們可以把它存到Amount屬性裡:
所以我們可以在Times方法裡使用它:
現在處理這個2,它應該可以使用引數multiplier代替:
OK!
此外,我們可以對程式碼的語法進行一些優化:
其實某些優化也應該通過TDD的RGR週期來實現。
第一篇文章就簡單介紹這些。