測試驅動開發實踐 - Test-Driven Development


一.前言

不知道大家有沒聽過“測試先行的開發”這一說法,作為一種開發實踐,在過去進行開發時,一般是先開發用戶界面或者是類,然后再在此基礎上編寫測試。

但在TDD中,首先是進行測試用例的編寫,然后再進行類或者用戶界面的開發。由於要先開發測試用例,那么開發人員就必須清楚測試的目的,所測功能模塊的業務邏輯以及需要測試的場景。

這樣TDD確保了項目的代碼與所需的業務是匹配的,並且在日后的開發工作中也能確保之前所做的功能的可測試性。

很多同學問TDD是使用那種編程語言,或者是某種技術,這里需要明確的是,TDD並不是某種技術,而是一種項目實踐。

 

導語:

 

傳統開發模式與TDD開發模式的區別在哪里?TDD開發的困難之處和優點是什么?TDD具體開發過程中又需要用到哪些技術知識點?且看本文作者通過實例來為你闡述TDD的開發流程,讓你對TDD有一個大致的了解。

 

二.傳統開發模式與TDD開發模式

1. 傳統開發模式流程:

項目代碼開發 -> 編寫測試用例 –> 運行測試用例 -> 修復代碼BUG

 

 

2. TDD開發模式流程

編寫測試用例 -> 運行測試用例 –> 編寫項目代碼 -> 運行測試用例 -> 重構代碼

 

 

三.TDD入門難題

說到寫測試用例,一般的同學都覺得沒啥問題,就根據已有的代碼,順着葫蘆摸瓜的就把該測試的方法都照着“套”一遍,測試有哪些結果也得順着項目的代碼來。

至於這測試代碼能不能測出項目的問題,那是另外的問題,關鍵是測試代碼要Pass,那我的工作才能算完。

但是要在還沒項目代碼之前寫測試用例,那就是等於我要憑空想象那些個抽象又晦澀難懂的功能點,還得要在心中勾勒出它們的輪廓以及細節,這不等於讓我自己畫個餅來充飢么?問題是我連這餅應該是啥樣子都還沒個譜,這是何等的悲涼啊,要是“藍胖子”在我身邊就好了~

 

 

 

四.TDD的優點

1. 保證代碼質量,鼓勵開發人員僅編寫滿足需求的代碼。

“李雷”同學號稱馬虎王,經常各種物品丟失,比如女友(對象)丟失;借用物品(引用)丟失;使用“IO自來水”之后不關閥門;去倉庫(Database)取貨,因為忘記某些物品,不得不頻繁往返等等。

“韓梅梅”同學功力深厚,精心打造出了一段瑞士軍刀般的代碼,耗時5天(其實此功能客戶無擴展需求僅要求1天時間完成)。

而在TDD實踐中,我們需要注重代碼質量,並編寫剛好適量的代碼。

 

2. 保證代碼與業務需求的一致性

一般來講程序員都願意把功能完美的體現在代碼上,可有時候天不隨人意,心里免不得擔憂,我這代碼能滿足業務需求么?但在TDD中,首先是進行測試用例的編寫,然后再進行類或者用戶界面的開發。由於要先開發測試用例,那么開發人員就必須清楚測試的目的,這樣TDD確保了項目的代碼與所需的業務是匹配的。

 

3. 創建簡明有針對性的接口

一日,“李雷”接到“韓梅梅”發來的為某個功能准備的闖關寶典和核心步驟(類庫與接口)。可讀了3000遍還是沒有能理解,一方面是“韓梅梅”采用了古代文言文與現代拉丁語的混搭來書寫核心步驟,另一方面“韓梅梅”的“韓”式1到1000000的命名規則讓“李雷”在讀了30秒核心步驟后,已經不知道第幾條是第幾條,。

在TDD實踐中,我們要注重創建有意義的、簡明的接口,因為這一點在與他人合作中尤其重要。

 

4. 與用戶溝通,明確需求

在開發代碼的過程中,我們總會有遇到不太明確的需求點,這個時候和需求人員溝通那是必不可少的,了解了功能的輸入和輸出才能保證完美的完成任務。在溝通的過程中也加深了與客戶的信任和默契度,不知不覺中還能提高EQ,一舉兩得。

 

5. 回歸測試,確保新的更改不影響現有功能

在“韓梅梅”同學開發某個功能3個月后,“李雷”接到上級指示,客戶要擴展該功能,但是原有功能保持不變。在苦心操勞了之后,“李雷”同學光榮的完成了任務,正准備接受大家贊譽時,“韓梅梅”跳出來向大家訴苦,那就是“李雷”為了做擴展功能把她之前做的功能給弄壞了,當時“李雷”那個心啊,拔涼拔涼的!

TDD的開發中加入了回歸測試,這樣就確保了之前的功能的正確與完整性,減少不必要的問題。

 

6. 提升系統的開放性和擴展性

一直以來我們做事都要講先后順序,軟件開發也有着類似的工序。“李雷”和“韓梅梅”被一起“充軍”到某緊急功能模塊上,並且“李雷”要等“韓梅梅”完成她的功能模塊才能開始自己的模塊。為了解決這個問題,項目組決定使用某些技術來解除他們的依賴關系,比如使用到IOC以及一些設計模式,讓他們能夠同時開發,之后再將兩人的功能模塊組裝到一起。

 

五.TDD開發中需要使用到的技術知識點單元測試、依賴注入框架和模擬對象

1. TDD的工作流

TDD的工作流經常被描述為“紅燈 -> 綠燈 -> 重構”:首先以一個未能通過的測試開始,隨后編寫足以通過該測試的代碼,然后再重構代碼。當然我們都不願意看到不能通過的測試CASE,當你再繼續編寫項目代碼,讓原本不能通過的測試CASE通過的時候,你會感覺心里有一絲絲的愜意,然后再將代碼優化重構,瞬間又有了些成就感。抿一口水,工作就這么快樂的完成了。

 

2. 偽對象、依賴注入框(DI/IOC)與模擬框架

就最簡單的實踐來說,比較常見的三層架構,UI層去調用業務邏輯層,業務邏輯層去調用數據持久層。

“韓梅梅”做業務層的代碼,“李雷”做數據層的代碼,於是乎“韓梅梅”變成了“黃世仁”,“李雷”就成了“楊白勞”,其中辛酸只有“李雷”知道!為了改變命運,“李雷”決定做個“假”的數據層對象(模擬對象)給“韓梅梅”用着,省的她每天都在那催命。

 

偽對象是對代替外部資源的簡單模擬,它通常會在調用一個方法時為該方法返回預定義響應,但通常不會根據輸入參數而改變響應。

於是乎“李雷”歡樂的開始了他的計划,把“韓梅梅”所需要的功能點都用接口來實現(interface),然后把這接口的方法在單獨的一個模擬類里面都只寫了個簡單的殼,里面的各種返回值都寫成“韓梅梅”想要的數據樣例,最后語重心長的對“韓梅梅”說:“東西拿走喜兒給我留下…”,“韓梅梅”當然是歡快的蹦到了自己的座位上。

“韓梅梅”拿到李雷給的偽對象后,徑直就用了起來,在所有需要偽對象的類里面都直接用了萬能的“New”關鍵字來實例化這個偽對象,這招兼顧了簡單與實惠,廣大程序員愛好者都愛這么干。

但是 “韓梅梅”后來慢慢意識到不太對,我有許多地方都用用到NEW字,那不是以后“李雷”完成了他所謂的真的對象以后,我還必須得改我的代碼,把我之前放進去的偽對象給替換為真正的對象?我這不是自己給自己找茬么。

“韓梅梅”趕緊找到帶着黑框身背雙肩包的師兄,細說了當前的苦衷。黑框師兄那舍得是師妹這么憂愁,趕緊拿出殺手鐧“控制反轉”中的一招“依賴注入”,讓使用類中僅保留被調用對象的接口,然后動態的注入實例給這接口,這樣子只要實現了這個接口的類都可以被任意替換使用,並且這個注入的動作一般是由某個框架來實現的,比如Autofac,、Unity或者Ninject等等。

這下子“韓梅梅”心里踏實了,管你“李雷,張雷,王雷”寫什么偽對象或者真的對象,只要你的對象實現了指定的接口,我都能使用,而且我還不用自己去手動創建這個對象,省心又省時。

模擬框架是一系列用於快速創建偽對象的API,它能減少重復的代碼,提高編碼效率,比較常用的為Rhino, NSubstitute, Moq等。

 

“李雷”和“韓梅梅”就這么和諧的合作,但是隨着任務的增多,發現問題來咯:

1)       “李雷”要手工做很多的偽對象給“韓梅梅”,任務繁重。

2)       每個對象的內部需求是不一樣的,“李雷”發現要用一種通用的格式來創建這些偽對象幾乎是不可能的。

3)       很多偽對象又依賴於其他的偽對象,這樣子簡直就是要讓崩潰。

4)       很多為對象內部有狀態需要保存,手動來寫代碼很難去維護這些狀態標示。

“李雷”好不容易通過創建偽對象來擺脫“韓梅梅”的每日請安,這又掉進了創建無數偽對象的漩渦之中。於是“李雷”橫渡遠洋,爬山涉水,期望能找到一盞明燈解決這些問題,終於功夫不負有心人,“李雷”找到了神兵利器去解決這個問題,那就是模擬框架。

模擬框架幫助“李雷”快速的創建了各種“韓梅梅”需要的模擬對象,以及各種所需的API,彈指一揮間,李雷用這神兵利器已經殺敵無數,嘴角不由得上揚了一番!

 

3. 重構代碼

但是問題總歸還是有得,她發現自己有些功能雖然測試通過,但是代碼寫的不好,經常被黑眼圈師兄批評,說她的代碼質量不高。

所以她必須盡可能在剛測試通過之后就盡可能的優化代碼,一來是少挨罵;二來也是提高自己編碼水平的一個機會,查漏補缺;三是貴人多忘事,何況是“我這等如花似玉的姑娘!”,如果不及早優化,恐怕以后很難有時間再來弄了。

因為已經有了測試代碼,所以重構代碼那也是很有保障的事情,如果我改錯東西了,那么我寫的測試用例肯定不能通過,這樣子也能讓我信心滿滿的去把這些個有臭味的代碼大卸八塊了。

 

六.工序流程

下面我們來看看“韓梅梅”和“李雷”他們的工作步驟:

1. 首先,韓梅梅和李雷分析了他們各自的業務,然后韓梅梅寫出了她需要測試用例,里面嘗試使用“李雷”將要提供的方法,並通過此方法獲取數據。當然這些代碼第一次是測試不通過的,因為里面需要的實現類還沒有寫。這里我們使用到Moq這樣一個模擬框架。

 

測試用例的運行結果,大家也是知道的,兩個字“悲催”!

 

 

2. 然后, “李雷”那邊開始了數據持久層接口的編寫(IProductRepository),“韓梅梅”拿到李雷提供的接口后,完成了業務邏輯層(ProductService)的代碼編寫,完畢之后大吐一口氣:“小伙子終於給力了一次!”。

A.  “李雷”的代碼如下,實際上“李雷”只是提供了接口(interface)給“韓梅梅”,他還並沒有開始編寫具體的實現類,但是韓梅梅已經可以通過該接口來工作了。

 

B. “韓梅梅”的服務類代碼如下,她獲取到“李雷”提供的數據持久層的接口后就開始歡快的編寫代碼,一切是那么的行雲流水啊:

 

 

3. 接下來“韓梅梅”添加了各種需要的引用,再次運行起了測試用例,這次順利的PASS了,心里那個激動,沒的說!

 

 

 

4. 工作快要接近尾聲,不過眼鏡師兄提醒過“廣大程序猿應該有高度的思想覺悟,不遺余力的提高代碼質量”,為了達成這一目標,“韓梅梅”又開始了上跳下竄的“大家來找茬”。

她發現里面有段代碼寫的不好,循環太多,也不夠整潔,她想優化下代碼,又怕把寫好邏輯弄壞了,不過現在有了測試用例,她不會再怕有這個問題,改錯代碼,測試用例自然也就無法通過。

   

再運行下測試用例,依然通過,此次代碼優化完畢,如果還有新的問題可以在依葫蘆畫瓢的繼續優化。     

 

 5. 與此同時,“李雷”那邊的數據持久層代碼也差不多寫好了,大家總得需要把代碼合起來作“集成測試”,這個時候就要用到IOC框架來把“李雷”編寫的數據層實例注入到業務邏輯層,注入實例使用的是Autofac這個IOC框架,我們這里使用構造函數注入,關於注入框架的更多信息,請讀者G….gle。

 

 

至此,“韓梅梅”與“李雷”各自的工作都完成了,大家也不在互相說啥,各自都優化了各自的功能代碼,快樂的工作繼續進行着,我們的TDD講解也到此結束!

 

參考文獻:

  1. Test Driven Development: By Example – Kent beck
  2. Refactoring: Improving the Design of Existing Code – Kent beck
  3. The Art of Unit Testing: With examples in .NET – Roy Osherove
  4. Professional test driven development with C# - James Bender, Jeff McWherter

 

本人最近開始學習TDD,藉此提升自己的能力。在學習過程會有一些心得體會,於是便會寫一些博客來記錄這些想法,有興趣的朋友可以和我一起交流學習。

QQ群: 32745894,歡迎大家加入討論!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM