軟件測試中單元測試和測試驅動(dòng)開(kāi)發(fā)(TDD)雜談
在一種傳統的結構化編程語(yǔ)言中,比如C,要進(jìn)行測試的單元一般是函數或子過(guò)程。在象C++這樣的面向對象的語(yǔ)言中, 要進(jìn)行測試的基本單元是類(lèi)。對Ada語(yǔ)言來(lái)說(shuō),開(kāi)發(fā)人員可以選擇是在獨立的過(guò)程和函數,還是在A(yíng)da包的級別上進(jìn)行單元測試。單元測試的原則同樣被擴展到第四代語(yǔ)言(4GL)的開(kāi)發(fā)中,在這里基本單元被典型地劃分為一個(gè)菜單或顯示界面。 經(jīng)常與單元測試聯(lián)系起來(lái)的另外一些開(kāi)發(fā)活動(dòng)包括代碼走讀(Code review),靜態(tài)分析(Static analysis)和動(dòng)態(tài)分析(Dynamic analysis)。靜態(tài)分析就是對軟件的源代碼進(jìn)行研讀,查找錯誤或收集一些度量數據,并不需要對代碼進(jìn)行編譯和執行。動(dòng)態(tài)分析就是通過(guò)觀(guān)察軟件運行時(shí)的動(dòng)作,來(lái)提供執行跟蹤,時(shí)間分析,以及測試覆蓋度方面的信息。
最近公司要求重新回顧單元測試的實(shí)際效果,作為一個(gè)開(kāi)發(fā)經(jīng)理,我個(gè)人對單元測試也有很多疑惑。就個(gè)人而言,我自己也寫(xiě)過(guò)很多單元測試,也鼓勵程序員寫(xiě)單元測試,但實(shí)際效果似乎不盡如人意。因此,寫(xiě)了這篇短文,想和大家一起探討。
1. 背景介紹
我所在的公司是一家外資軟件公司,主要工作是開(kāi)發(fā)一個(gè)復雜的在線(xiàn)系統(java based web applicaiton). 該系統的主要特點(diǎn)是:定制化程度比較高,業(yè)務(wù)邏輯相當復雜。系統的技術(shù)棧是Struts, EJB (JBoss)and Hibernate。我管理的小組一共有10個(gè)左右開(kāi)發(fā)人員,6個(gè)左右測試人員,平均工作經(jīng)驗在3年以上。
公司在兩年前開(kāi)始推行單元測試。在開(kāi)始推行單元測試之前,系統已經(jīng)正式上線(xiàn),也就是意味著(zhù)有海量的沒(méi)有單元測試的代碼。推行之后,應該說(shuō)投入了相當多的時(shí)間,總共覆蓋的行數有20k, 其中行覆蓋率(line coverage)有55%左右,分支覆蓋率(branch coverage)有40%左右。我相信經(jīng)過(guò)這么多嘗試,應該說(shuō),我帶的這個(gè)組不是一個(gè)單元測試的新手,有資格討論單元測試的得失。
2. 實(shí)踐中的問(wèn)題和疑惑(開(kāi)發(fā)人員怎么說(shuō)?)
我一向主張:一項技術(shù)值不值得或者好不好用歸根結底是要問(wèn)實(shí)際的使用者和開(kāi)發(fā)者的。作為一個(gè)經(jīng)理,我不傾向于推行一項程序員極力反對的技術(shù),不管這項技術(shù)是不是業(yè)界的標準或者是評論者的寵兒。一項技術(shù)必須要解決實(shí)際問(wèn)題,也就是mark your life easier。所以下面是開(kāi)發(fā)人員的回答。
2.1為什么需要單元測試和TDD (Test Driven Development)?
2.2.1 單元測試可以發(fā)現代碼缺陷(Defect)么?投入/產(chǎn)出比(Defect count/Effort)是多少?
只能發(fā)現待測單元的缺陷,不能發(fā)現單元交互(集成)之間的缺陷。在實(shí)踐過(guò)程中,很少有defect通過(guò)單元測試發(fā)現。
基本不能用于發(fā)現表現層(JSP, Java scripts, css, UI etc)的代碼缺陷投入/產(chǎn)出比太高。換句話(huà)說(shuō),相比于單元測試,人工測試(munual testing)可以很大程度得更快更好的發(fā)現系統缺陷。
2.2.2單元測試可以用來(lái)防止Regression Defect么?
如果我們特地為某個(gè)regression defect加了相應的單元測試,那么單元測試在某種程度上可以防止regression defect的再一次出現。但是同樣的,單元測試只能防止待測單元中的Regression Defect,而且需要通過(guò)猜測來(lái)加入相應的測試案例。
2.2.3 單元測試對設計有幫助么?
單元測試本身不一定能幫助設計。據說(shuō)TTD可以幫助設計,實(shí)踐過(guò)程中沒(méi)有很深的體會(huì )。
2.2.4你投入了多少時(shí)間寫(xiě)單元測試?需要多少時(shí)間維護單元測試?
單元測試:代碼= 2:1,也就是說(shuō)一行代碼需要兩行單元測試。也有些人說(shuō)1:1。
維護成本基本上決定于單元接口的變化頻率:對于一些比較穩定的代碼單元,維護成本還可以接受。但對于一些需求變化劇烈的單元,基本上需要重寫(xiě)。在實(shí)際實(shí)踐中,可能的比例為穩定的單元測試:重寫(xiě)的單元測試 = 80%:20%。但是這里有一個(gè)悖論:其實(shí)我們更希望單元測試可以用于驗證(verify)核心單元的正確性,然而這些單元的測試單元確基本上需要重寫(xiě)。這是為什么呢?其中一個(gè)可能的原因是:對于一個(gè)在線(xiàn)系統(web based application)來(lái)說(shuō),系統的主要邏輯和用戶(hù)接口(user interface)綁定過(guò)于緊密,所以,用戶(hù)接口的變化導致從表現層到數據庫層的垂直變化。即使業(yè)務(wù)需求只是加了一個(gè)新的屬性,但是這個(gè)數據將被加入核心的對象當中,所有涉及這個(gè)對象的單元測試需要改變。
2.2.5單元測試的主要挑戰是什么?
挑戰之一:如何在多個(gè)測試用例之間共享測試數據。
公司產(chǎn)品支持一個(gè)很復雜的在線(xiàn)向導,由七步組成,每一步可以單獨保存然后退出,下次繼續編輯。如果你想測試最后一步的API,你需要準備很多其他頁(yè)面的數據。因此,需要花很多時(shí)間準備測試數據。另外,公司產(chǎn)品還支持相似功能的其他向導。作為一個(gè)程序員,我們一直想在多個(gè)類(lèi)似功能的向導API之間共享測試數據。然而,如果待測對象本身有些微變化,所有共享該數據的測試代碼全部需要重寫(xiě)。這是一個(gè)巨大的維護費用。
挑戰之二:劇烈的需求變化導致維護成本劇增,收益減少。
正如2.2.4中描述的,一個(gè)典型的在線(xiàn)系統(web based application),通?梢苑譃槿龑樱罕憩F層,主要是用戶(hù)界面,包括HTML/JSP/CSS/Java Scripts/Ajex等等;業(yè)務(wù)層,主要是業(yè)務(wù)邏輯;數據層,存取數據。根據面向對象設計(OOD)的原則,業(yè)務(wù)層主要由一組領(lǐng)域對象(Business Object/Domain Object)構成。這些領(lǐng)域對象只提供一組數目相對有限的,接口比較清晰的,時(shí)間比較穩定的API。對這組API進(jìn)行單元測試是有必要的,也是有意義的。
然而,系統還有相當一部分的代碼用于調用不同領(lǐng)域對象之間的API,轉變成表現層需要的對象。表現層其他的邏輯還包含大量的代碼用于連接不同的頁(yè)面,以及構建不同的向導。
正如和絕大多數的系統一樣,產(chǎn)品需求的變化是極其劇烈的,可以預測的,不可避免的。在這種情況下,需求變化將導致領(lǐng)域對象API以上的代碼(包括絕大多數表現層代碼和一部分業(yè)務(wù)層代碼)將發(fā)生劇烈變化,與之相應的單元測試代碼都需要相應的改變。也就是說(shuō),這些代碼的單元測試代碼的維護成本很好。
挑戰之三:海量的遺留代碼(Legacy Codes)
正如前面描述的,我們是在產(chǎn)品已經(jīng)上線(xiàn)之后才開(kāi)始推行單元測試的。因此,大量的遺留代碼并不適用于單元測試。換句話(huà)說(shuō),單元測試必須要在A(yíng)PI實(shí)現之前予以仔細得考慮。如果API本身沒(méi)有得到很好的設計,單元測試基本上是不可能的。
2.2.6 拿什么來(lái)衡量單元測試?
一般來(lái)說(shuō),業(yè)界使用行覆蓋率(line coverage)和分支覆蓋率(branch coverage)來(lái)衡量單元測試的測量。但在實(shí)際過(guò)程中,我們發(fā)現這些衡量標準和我們對單元測試的期望有很大差距:比如說(shuō),高覆蓋率不見(jiàn)得較少的代碼缺陷。高覆蓋率也不能防止regression缺陷。高覆蓋率也似乎和設計沒(méi)有直接關(guān)聯(lián)。從另外一個(gè)角度說(shuō),達到高覆蓋率所花費的時(shí)間也是相當驚人的。
從另外一個(gè)角度來(lái)說(shuō),我們希望找到一個(gè)方法可以簡(jiǎn)單直接地衡量單元測試的測量:比如說(shuō)代碼缺陷或regression defect數量。
3. 聆聽(tīng)和討論(業(yè)界怎么說(shuō))
帶著(zhù)這些問(wèn)題和困惑,我在網(wǎng)上查詢(xún)了大量相關(guān)資料,牛人的文章和業(yè)界的討論。很容易看出,業(yè)界對于單元測試的目標,作用,方法和手段都有很多爭議。
3.1 什么是(不是)單元測試?
3.1.1 單元測試和發(fā)現缺陷無(wú)關(guān)(http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/)
【摘要】單元測試不是一個(gè)發(fā)現缺陷或者檢測regression defect的有效方法。其一,單元測試,根據定義,是用來(lái)測試特定代碼單元。然而,一個(gè)系統往往是一個(gè)大量單元的復雜集成,單元測試很難發(fā)現集成的缺陷。其二,相比單元測試,手工測試或者自動(dòng)化集成測試更容易用于檢測缺陷。
文章來(lái)源于領(lǐng)測軟件測試網(wǎng) http://kjueaiud.com/