1、什么是TDD
我第一次接觸TDD這個概念,是在<<代碼整潔之道>>中,作者鮑勃大叔在書中,寫了一些關于測試代碼的代碼規范,其實就提到了有關TDD三定律:
- 定律一: 在編寫不能通過的單元測試前,不可編寫生產代碼
- 定律二: 只可編寫剛好無法通過的單元測試,不能編譯也算不能通過
- 定律三:只可編寫剛好足以通過當前失敗測試的生存代碼
我第一次讀到這三個定律時,不能說是毫無頭緒,只能說是一臉懵逼。
完全不知道作者想表達啥意思,也沒有案例代碼。
對此,我不得不網上查閱的很多相關文章,最后總結出來。
這里測試驅動開發里的驅動是做動詞,不是名詞
好了,現在如果別人問你TDD是什么,你就可以直接這樣告訴他。
2、傳統開發方式
我們傳統開發一個功能是這么開發的?
傳統編碼方式
需求分析,想不清楚細節,管他呢,先開始寫
發現需求細節不明確,去跟業務人員確認
確認好幾次終于寫完所有邏輯
運行起來測試一下,靠,果然不工作,調試
調試好久終于工作了
終于,代碼可以工作了
一看代碼爛的像坨屎,不敢動,動了還得手工測試,還得讓 QA 測試,還得加班…
傳統的開發方式,都是以開發為主,直接開始編寫代碼,代碼出了問題,再去改,多改幾次,你就會覺得這代碼簡直就是屎山,想重構一下,又怕出新的問題。
3、TDD步驟
而TDD測試驅動開發是怎么做的呢?
TDD要求我們先根據需求去拆分任務,把一個大的任務拆分位各個模塊,也就是一個個的函數,我們再去為這些函數去編寫最小的測試,再去寫能讓這個最小的測試通過的最小的實現。
TDD的生命周期圖如下。
這樣做的好處是:
1、有助于我們提前澄清需求
2、可以通過單元測試斷言的診斷機制快速得出反饋
3、當我們寫完了所有的需求,會發現所有的需求都會被測試覆蓋了
4、舉個例子
正所謂,光說不練,假把式;下來我們來整個簡單的例子去理解一下測試驅動開發;
假如我需要寫個功能,分析用戶上傳的文本中,每個單詞的數量,并且按照數量倒序排序,這個應該怎么實現:
比如說文本如下:
Hello world Hello CSDN Hello Boy My name is Boy is is |
那輸出就是:
Hello 3 is 3 Boy 2 world 1 CSDN 1 My 1 name 1 |
如果是新手或者是完全不懂代碼設計的人拿到這樣的功能,可能會這樣寫:
public static void main(String[] args) throws Exception{ String words = ""; File file=new File("word.txt"); Scanner sc=new Scanner(file); while(sc.hasNextLine()){ String line=sc.nextLine(); words = words + line + " "; } System.out.println(words); String[] wordArrays = words.split(" "); HashMap<String,Integer> hashMap = new HashMap<>();
for(int i=0;i<wordArrays.length;i++){
|
好了,寫完了,讓我們運行一下:
運行結果貌似也沒有問題,好了提交代碼。
真的沒有問題嗎?統計單詞的數目也沒有問題,但是,如果后期,你要對這段代碼做維護,要去修改這段代碼,這段代碼讀起來是什么感覺?在我看來,這段代碼,就是屎山。
代碼里幾乎完全沒有注釋,讀這段代碼,得從頭開始往下讀,如果其中一段代碼出了問題,你必須在整段代碼中尋找錯誤,非常浪費時間。
讓我們來試試看TDD的寫法,TDD要求首先要把一個功能,去拆分分成各個小功能,然后去為這些小功能寫測試用例。
這個統計單詞數目的代碼應該怎么拆分,試著拆分成小功能一下(這里要注意一下,同樣的功能,在拆分模塊的時候,不同的人選擇的拆分方法可能不同,一千個人里有一千個哈姆雷特,一千個人里也有一千種拆分方法)
拆分好后,我們就可以為這些功能編寫測試用例了,
我們先編寫測試用例,用assert斷言測試用例是否通過,運行,我們可以看出,方法還沒有進行開發,顯示未通過。
這一步,就是TDD定律二中規定的
- 定律二: 只可編寫剛好無法通過的單元測試,不能編譯也算不能通過 |
下一步要做什么?我們看定律一
- 定律一: 在編寫不能通過的單元測試前,不可編寫生產代碼 |
定律一反過來是什么意思,不就是編寫好不能通過的單元測試后,就可以開始編寫生產代碼了嗎?
于是開始編寫生產代碼進行測試
在這里,我們編寫好了方法,在執行測試用例后,顯示綠色,代表測試用例通過。
這時候又滿足了第三定律
- 定律三:只可編寫剛好足以通過當前失敗測試的生存代碼 |
我們的測試用例,專門測試一個小的功能,只為了通過這個方法。
下一步我們再重復以上步驟,去TDD其他模塊。
直到TDD完所有模塊,我們的功能就開發完了。
代碼如下:
public static void main(String[] args) throws Exception { //讀取文件 File file=new File("word.txt"); String words = readFile(file); String[] s1 = words.split(" "); //單詞記錄到hashmap HashMap<String, Integer> stringIntegerHashMap = groupHashMap(s1); //排序 List<Map.Entry<String, Integer>> entries = orderHashMap(stringIntegerHashMap); //輸出 System.out.println(entries); } |
方法的代碼太長,太展篇幅就不粘貼了,你看我們新的main方法,代碼就比較簡介,如果出了問題,只可能在這三個方法中其中,我們可以快速定位到方法中去,并且可以用之前編寫的測試用例進行測試。
5、總結
關于TDD測試驅動開發,我感覺精髓就是對功能進行拆分,用測試用例去測試功能,我相信很多人,都不會去編寫測試用例,代碼寫好后,就去頁面上點幾下,其實這是不太好的。因為如果這里的功能改動比較頻繁,你每次去頁面上通過點擊的方式測試功能,你得打開瀏覽器,登錄地址,尋找IP,為功能配置參數,這一套下來,真的非常耗費時間。
一次如果5分鐘,10次就是50分鐘。
而TDD建議的是什么?建議通過測試用例的方式去測試,它要求你必須編寫好測試用例后再去寫代碼,這樣就能保住,你每個小功能,都有一個測試用例,這樣,之后你改一個地方,只要找到這個地方所對應的測試用例就能測試了,非常方便。
當然,TDD這種開發方法其實弊端也是很明顯的,比如,大多數程序員,其實是怎么做測試的?就是直接重啟項目,去頁面上看看,功能對不對,測試用例?那是什么?我不是開發嗎,我又不是測試。我去頁面點幾下測試,可能只需要幾分鐘,我去配置測試用例,八成等得二十分鐘,所以大多數程序員可能還是會選擇通過頁面點擊的方式去測試。
測試用例真的是沒有必要的嗎?如果你去新建一個Maven項目,你會發送,test目錄和main目錄是同一級別的
我相信在設計的時候,可能設計者(設計Maven的程序員肯定是大佬中的大佬),也是認為測試用例是非常重要的,才會把test目錄放在這個位置吧。
原文轉自:http://www.uml.org.cn/Test/202204264.asp