單元測試框架進行自動化測試
最近參與的一個項目里我把單元測試放到很重要的位置并且也發(fā)現(xiàn)了一些問題。順便介紹一下單元測試框架進行自動化測試的方面
這不是一篇嚴(yán)謹(jǐn)?shù)募夹g(shù)文章。只是一些個人不成熟的感想。
在實際開發(fā)過程中,我發(fā)現(xiàn)在單元測試代碼中經(jīng)常會出現(xiàn)兩種情況:***種就是在測試代碼中炫耀編程技巧,第二種就是敷衍了事,你不是讓我通過測試么?好,我就寫一個用例,一定能通過的那種,然后告訴你,OK,我的測試通過了。我覺得,這就是對單元測試的意義沒有真正理解的表現(xiàn)。
到底單元測試是做什么用的?我想,在說明這個問題之前,我先說說我所理解的測試到底是做什么用的。
所謂的測試,是一種產(chǎn)品質(zhì)量保證的手段。我按照需求規(guī)格說明書制造了一件產(chǎn)品,那么誰來確保這個產(chǎn)品符合了需求規(guī)格的要求呢?就是測試。它會根據(jù)需求規(guī)格說明書設(shè)計一系列的場景和用例,來對產(chǎn)品進行測試,看看產(chǎn)品是不是真的符合所期望的需求。
要達(dá)到這個目標(biāo),其實并不十分的容易,因為一個真正的系統(tǒng),情況十分復(fù)雜,里面充滿了數(shù)不清的分支、異常、邊界條件,甚至運行環(huán)境,將這些東西組合起來,產(chǎn)生的需要測試的點將會是一個天文數(shù)字,在有限的時間內(nèi)做完一個充分而可靠的測試,是不可能的。
為了將充分測試變得可能,一個比較好的途徑就是分層測試。我在做運行測試或性能測試的時候,有一個前提,就是假設(shè)整個系統(tǒng)的集成運行已經(jīng)沒有問題了,在運行測試或性能測試時,我將不再考慮“系統(tǒng)無法正常運行”這種場景。那么如何保證集成運行沒問題呢?我們用集成測試來檢驗。但是在做集成測試的時候,我們同樣要基于一個假定,就是各個模塊的功能都能夠如期正常工作。而這一點,又是通過模塊自身的功能測試來完成的?!@樣一層層往下推,每個層次就假設(shè)它所依賴的層次沒有問題,這樣就可以減少很多場景以及由這些場景引出的額外的分支。將原先一個幾何級數(shù)的測試用例分解成可以接受的若干層次的算術(shù)級數(shù)的用例。這樣一來測試就變得有可能做好了。
而單元測試,正是這些測試的***層次——保證每個函數(shù)/方法,或者說最小功能模塊的正確性的一種測試。
通過上面的描述,我們至少清楚了這樣幾件事情:
1. 單元測試是一種測試,它不是代碼的一部分;
2. 單元測試是***層級的測試,它只保證函數(shù)的可靠性,不保證其它;
3. 單元測試應(yīng)該能保證每一個函數(shù)的可靠性。
單元測試是一種測試,所以,我們應(yīng)該以一種測試的眼光去面對它——我們要測試正常情況,邊界條件,要對它的測試目標(biāo)——函數(shù)做黑盒分析,白盒分析,選擇合適的測試數(shù)據(jù),構(gòu)建測試場景和測試環(huán)境——總之,一切測試應(yīng)該做的事情,單元測試都不應(yīng)該省略。
理論上來說,單元測試和其他測試一樣,也是可以純手工完成的:我們可以寫一段某函數(shù)的測試代碼,然后輸入我們的測試輸入,觀察測試輸出,并跟期望值做比較——事實上這種人工測試,寫了一段時間代碼的人應(yīng)該都不會陌生。但是,單元測試有一點特殊性,就是在一個系統(tǒng)中,函數(shù)會非常非常的多,變化也比軟件的功能頻繁的多。面對這么多的函數(shù),這么頻繁的變化,純手工測試是不現(xiàn)實的。所以,我們必須要引入單元測試框架進行自動化測試。注意,這里的單元測試框架只是實現(xiàn)自動化測試的一個手段,對單元測試本身并不產(chǎn)生任何影響——沒有單元測試框架,單元測試一樣也是可以進行的,只是會痛苦很多。
單元測試框架引入的目的只是為了自動化單元測試,簡化單元測試的步驟。所以,對于測試代碼的編寫,我們的重點應(yīng)該是:1、如何搭建測試環(huán)境、測試場景;2、如何選擇測試用例;3、如何校驗測試結(jié)果。對于測試代碼本身,應(yīng)該盡可能的簡單,能不要使用技巧盡量不要使用,我們的目的在于測試,如果測試本身過于復(fù)雜,我們不能保證測試的正確性,測試這個工作就白做了。
另外,剛剛提到單元測試是對函數(shù)的測試,因此,測試必須是以函數(shù)為單位的。每個函數(shù)應(yīng)該擁有自己單獨的一個測試,但是在這個測試中,我們應(yīng)該針對這個函數(shù)的各個方面:正常的、異常的、邊界的……等等,各個方面進行完善的測試,這樣我們才能保證這個函數(shù)的功能是如我們所愿的。但是單元測試不需要負(fù)責(zé)函數(shù)的組合工作情況。那應(yīng)該是(低層次)功能測試的工作,而不是單元測試的工作。這個功能測試就是在假定所有函數(shù)都工作正常的基礎(chǔ)之上,對這些函數(shù)組合形成的功能模塊進行測試。這種測試,視情況而定,可以使用單元測試框架,也可以使用其他自動化測試方法或者甚至是使用純?nèi)斯y試。
另外,我還想討論一下單元測試的編寫和運行。
絕大部分時候,單元測試的編寫,是由開發(fā)人員做的。我們在以前某次對單元測試的討論中,甚至有人認(rèn)為,單元測試必須由開發(fā)人員完成,而不應(yīng)該由獨立的測試人員完成。對于這個問題,我是這樣看的:測試是一種針對需求的驗證工作。如果這個需求非常清晰,清晰到開發(fā)人員之外的人都可以輕易掌握(有些日本外包發(fā)出來的函數(shù)說明書就能達(dá)到這一點),這時單元測試可以由獨立的測試人員完成。但是大部分情況下對于函數(shù)級別,做不到這一點。這時最清楚函數(shù)需求的人就是開發(fā)人員本人,在這種情況下當(dāng)然就應(yīng)該是開發(fā)人員自己編寫測試用例。但是開發(fā)人員必須搞清楚自己身兼兩個不同的角色:運動員(實現(xiàn)代碼)和裁判員(檢驗代碼),在編寫測試用例的時候絕不能假定任何函數(shù)的實現(xiàn),而應(yīng)該完全按照它應(yīng)該有的需求來做。這樣才能做好單元測試這件事。很多時候單元測試形同虛設(shè),就是因為開發(fā)人員沒有很好的轉(zhuǎn)換自己的角色造成的。
單元測試的運行,目前我們這個Python的項目比較容易,直接運行模塊就是該模塊的單元測試,而以模塊形式import就是實際使用。對于像C++或者其他的一些語言來說,可能沒有這樣方便的形式。我們可以把測試寫在獨立的文件中,然后用makefile組合不同的項目和主函數(shù)來做到這一點。另外還有一點就是,實際運行過程中可能會有一些環(huán)境,這些環(huán)境在測試時難以獲得,或者增加上去之后,就難以測試(比如網(wǎng)絡(luò)環(huán)境、數(shù)據(jù)庫環(huán)境等等),這時我們可以采用一些虛擬的環(huán)境來做到。我們把運行時需要的環(huán)境做一個簡化的虛擬版本,然后以這個版本作為測試環(huán)境進行測試,對于Python來說,我們可以實現(xiàn)這樣的一個庫在測試時import進來并且同時做一些環(huán)境初始化工作,在C++里,我們可以專門為測試寫一些運行庫,在實際運行編譯和測試編譯時,鏈接不同的庫。這在自動化測試技術(shù)中有個專門的名稱叫做 Mock Object。關(guān)于這個,我就不再深入了。
【編輯推薦】