天天看點

測試驅動開發實踐

一.前言

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

但在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講解也到此結束!

繼續閱讀