高端網(wǎng)站設(shè)計(jì)費(fèi)用電商網(wǎng)站建設(shè)公司
01、前言
很早之前,曾在網(wǎng)絡(luò)上見到過 TDD 這 3 個(gè)大寫的英文字母,它是 Test Driven Development 這三個(gè)單詞的縮寫,也就是“測(cè)試驅(qū)動(dòng)開發(fā)”的意思——聽起來很不錯(cuò)的一種理念。
其理念主要是確保兩件事:
- 確保所有的需求都能被照顧到。
- 在代碼不斷增加和重構(gòu)的過程中,可以檢查所有的功能是否正確。
但后來很長(zhǎng)一段時(shí)間里,都沒再聽過 TDD 的消息。有人說,TDD 已經(jīng)死了,給出的意見如下:
1)通常來說,開發(fā)人員不應(yīng)該在沒有失敗的測(cè)試用例下編寫代碼——這似乎是合理的,但是它可能導(dǎo)致過度測(cè)試。例如,為了保證一行生產(chǎn)代碼的正確性,你不由得寫了 4 行測(cè)試代碼,這意味著一旦這一行生產(chǎn)代碼需要修改,你也得修改那 4 行測(cè)試代碼。
2)為了遵循 TDD 而寫的代碼,容易進(jìn)入一個(gè)誤區(qū):代碼是為了滿足測(cè)試用的,而忽略了實(shí)際需求。
02、TDD 到底是什么?
不管 TDD 到底死了沒有,先讓我們來回顧一下 TDD 到底是什么。
TDD 的基本思想就是在開發(fā)功能代碼之前,先編寫測(cè)試代碼。也就是說在明確要開發(fā)某個(gè)功能后,首先思考如何對(duì)這個(gè)功能進(jìn)行測(cè)試,并完成測(cè)試代碼的編寫,然后編寫相關(guān)的代碼滿足這些測(cè)試用例。然后循環(huán)進(jìn)行添加其他功能,直到完成全部功能的開發(fā)。
TDD 的基本過程可以拆解為以下 6 個(gè)步驟:
1) 分析需求,把需求拆分為具體的任務(wù)。
2) 從任務(wù)列表中取出一個(gè)任務(wù),并對(duì)其編寫測(cè)試用例。
3) 由于沒有實(shí)際的功能代碼,測(cè)試代碼不大可能會(huì)通過(紅)。
4) 編寫對(duì)應(yīng)的功能代碼,盡快讓測(cè)試代碼通過(綠)。
5) 對(duì)代碼進(jìn)行重構(gòu),并保證測(cè)試通過(重構(gòu))。
6) 重復(fù)以上步驟。
可以用下圖來表示上述過程。
?
03、TDD 的實(shí)踐過程
通常情況下,我們都習(xí)慣在需求分析完成之后,盡快地投入功能代碼的編寫工作中,之后再去調(diào)用和測(cè)試。
而 TDD 則不同,它假設(shè)我們已經(jīng)有了一個(gè)“測(cè)試用戶”了,它是功能代碼的第一個(gè)使用者,盡管功能代碼還不太完善。
當(dāng)我們站在“測(cè)試用戶”的角度去寫測(cè)試代碼的時(shí)候,我們要考慮的是,這個(gè)“測(cè)試用戶”該如何使用功能代碼呢?是通過一個(gè)類直接調(diào)用方法呢(靜態(tài)方法),還是構(gòu)建類的實(shí)例去調(diào)用方法呢(實(shí)例方法)?這個(gè)方法如何傳參呢?方法如何命名呢?方法有返回值嗎?
有了測(cè)試代碼后,我們開始編寫功能代碼,并且要以最快地速度讓測(cè)試由“紅”變?yōu)椤熬G”,可能此時(shí)的功能代碼很不優(yōu)雅,不過沒關(guān)系。
當(dāng)測(cè)試通過以后,我們就可以放心大膽的對(duì)功能代碼進(jìn)行“重構(gòu)”了——優(yōu)化原來比較丑陋、臃腫、性能偏差的代碼。
接下來,假設(shè)我們接到了一個(gè)開發(fā)需求:
汪汪隊(duì)要到小鎮(zhèn)冒險(xiǎn)島進(jìn)行表演,門票為 99 元,冒險(xiǎn)島上唯一的一個(gè)程序員王二需要開發(fā)一款可以計(jì)算門票收入的小程序。
按照 TDD 的流程,王二需要先使用 Junit 編寫一個(gè)簡(jiǎn)單的測(cè)試用例,測(cè)試預(yù)期是:銷售一張門票的收入是 99 元。
public?class?TicketTest?{private?Ticket?ticket;@Beforepublic?void?setUp()?throws?Exception?{ticket?=?new?Ticket();}@Testpublic?void?test()?{BigDecimal?total?=?new?BigDecimal("99");assertEquals(total,?ticket.sale(1));}}
為了便于編譯能夠順利通過,王二需要一個(gè)簡(jiǎn)單的 Ticket 類:
public?class?Ticket?{public?BigDecimal?sale(int?count)?{return?BigDecimal.ZERO;}}
測(cè)試用例運(yùn)行結(jié)果如下圖所示,紅色表示測(cè)試沒有通過:預(yù)期結(jié)果是 99,實(shí)際結(jié)果是 0。
那接下來,王二需要快速讓測(cè)試通過,Ticket.sale()
?方法修改后的結(jié)果如下:
public?class?Ticket?{public?BigDecimal?sale(int?count)?{if?(count?==?1)?{return?new?BigDecimal("99");}return?BigDecimal.ZERO;}}
再運(yùn)行一下測(cè)試用例,結(jié)果如下圖所示,綠色表示測(cè)試通過了:預(yù)期結(jié)果是 99,實(shí)際結(jié)果是 99。
綠了,綠了,測(cè)試通過了,到了該重構(gòu)功能代碼的時(shí)候了。99 元是個(gè)魔法數(shù)字,至少應(yīng)該聲明成常量,對(duì)吧?
public?class?Ticket?{private?final?static?int?PRICE?=?99;public?BigDecimal?sale(int?count)?{if?(count?==?1)?{return?new?BigDecimal(PRICE);}return?BigDecimal.ZERO;}}
重構(gòu)完后再運(yùn)行一下測(cè)試用例,確保測(cè)試通過的情況下,再增加幾個(gè)測(cè)試用例,比如說門票銷量為負(fù)數(shù)、零甚至一千的情況。
public?class?TicketTest?{private?Ticket?ticket;@Beforepublic?void?setUp()?throws?Exception?{ticket?=?new?Ticket();}@Testpublic?void?testOne()?{BigDecimal?total?=?new?BigDecimal("99");assertEquals(total,?ticket.sale(1));}@Test(expected=IllegalArgumentException.class)public?void?testNegative()?{ticket.sale(-1);}@Testpublic?void?testZero()?{assertEquals(BigDecimal.ZERO,?ticket.sale(0));}@Testpublic?void?test1000()?{assertEquals(new?BigDecimal(99000),?ticket.sale(1000));}}
銷量為負(fù)數(shù)的時(shí)候,王二希望功能代碼能夠拋出異常;銷量為零的時(shí)候,功能代碼的計(jì)算結(jié)果應(yīng)該為零;銷量為一千的時(shí)候,計(jì)算結(jié)果應(yīng)該為 99000。
有兩個(gè)測(cè)試用例沒有通過,那么王二需要繼續(xù)修改功能代碼,調(diào)整如下:
public?class?Ticket?{private?final?static?int?PRICE?=?99;public?BigDecimal?sale(int?count)?{if?(count?<?0)?{throw?new?IllegalArgumentException("銷量不能為負(fù)數(shù)");}if?(count?==?0)?{return?BigDecimal.ZERO;}if?(count?==?1)?{return?new?BigDecimal(PRICE);}return?new?BigDecimal(PRICE?*?count);}}
再運(yùn)行一下測(cè)試用例,發(fā)現(xiàn)都通過了。又到了重構(gòu)的時(shí)候了,銷量為零、或者大于等于一的時(shí)候,代碼可以合并,于是重構(gòu)結(jié)果如下:
public?class?Ticket?{private?final?static?int?PRICE?=?99;public?BigDecimal?sale(int?count)?{if?(count?<?0)?{throw?new?IllegalArgumentException("銷量不能為負(fù)數(shù)");}return?new?BigDecimal(PRICE?*?count);}}
重構(gòu)結(jié)束后,再運(yùn)行測(cè)試用例,確保重構(gòu)后的代碼依然可用。
04、最后
從上面的實(shí)踐過程可以得出如下結(jié)論:
TDD 想要做的就是讓我們對(duì)自己的代碼充滿信心,因?yàn)槲覀兛梢酝ㄟ^測(cè)試代碼來判斷這段代碼是否正確無誤。
也就是說,TDD 流程比較關(guān)鍵的一環(huán)在于如何寫出有效的測(cè)試代碼,這里有 4 個(gè)原則可以參考:
1)測(cè)試過程應(yīng)該盡量模擬正常使用的過程。
2)應(yīng)該盡量做到分支覆蓋。
3)測(cè)試數(shù)據(jù)應(yīng)該盡量包括真實(shí)數(shù)據(jù),以及邊界數(shù)據(jù)。
4)測(cè)試語句和測(cè)試數(shù)據(jù)應(yīng)該盡量簡(jiǎn)單,容易理解。
注意,這 4 個(gè)原則不僅適用于 TDD,同樣適用于任何流程下的單元測(cè)試。
最后,我想說的是,不管 TDD 有沒有死,TDD 都不是銀彈,不可能適合所有的場(chǎng)景,但這不應(yīng)該成為我們拒絕它的理由。
【B站最全最易學(xué)】十年大佬終于將測(cè)試開發(fā)路線整理出來了,小白一學(xué)就會(huì),拿走不謝,允許白嫖!!