深入理解Python單元測試unittest的使用示例
軟件測試
大型軟件系統(tǒng)的開發(fā)是一個很復(fù)雜的過程,其中因為人的因素而所產(chǎn)生的錯誤非常多,因此軟件在開發(fā)過程必須要有相應(yīng)的質(zhì)量保證活動,而軟件測試則是保證質(zhì)量的關(guān)鍵措施。正像軟件熵(software entropy)所描述的那樣:一個程序從設(shè)計很好的狀態(tài)開始,隨著新的功能不斷地加入,程序逐漸地失去了原有的結(jié)構(gòu),最終變成了一團(tuán)亂麻(其實最初的"很好的狀態(tài)"得加個問號)。測試的目的說起來其實很簡單也極具吸引力,那就是寫出高質(zhì)量的軟件并解決軟件熵這一問題。
可惜的是,軟件開發(fā)人員很少能在編碼的過程中就進(jìn)行軟件測試,大部分軟件項目都只在最終驗收時才進(jìn)行測試,有些項目甚至根本沒有測試計劃!隨著軟件質(zhì)量意識的增強(qiáng),許多軟件開發(fā)組織開始轉(zhuǎn)向UML、CMM、RUP、XP等軟件工程方法,以期提高軟件質(zhì)量,并使軟件開發(fā)過程更加可控,好在這些方法對測試都提出了很嚴(yán)格的要求,從而使得測試在軟件開發(fā)過程的作用開始真正體現(xiàn)出來。
軟件測試作為一種系統(tǒng)工程,涉及到整個軟件開發(fā)過程的各個方面,需要管理人員、設(shè)計人員、開發(fā)人員和測試人員的共同努力。作為軟件開發(fā)過程中的主要力量,現(xiàn)今的程序員除了要編寫實現(xiàn)代碼外,還承擔(dān)著單元測試這一艱巨任務(wù),因此必須采用新的工作模式:
- 編寫和維護(hù)一套詳盡的單元測試用例;
- 先構(gòu)造單元測試和驗收測試用例,然后再編寫代碼;
- 根據(jù)構(gòu)造的測試用例來編寫代碼。
單元測試負(fù)責(zé)對最小的軟件設(shè)計單元(模塊)進(jìn)行驗證,它使用軟件設(shè)計文檔中對模塊的描述作為指南,對重要的程序分支進(jìn)行測試以發(fā)現(xiàn)模塊中的錯誤。由于軟件模塊并不是一個單獨的程序,為了進(jìn)行單元測試還必須編寫大量額外的代碼,從而無形中增加了開發(fā)人員的工作量,目前解決這一問題比較好的方法是使用測試框架。測試框架是在用XP方法進(jìn)行單元測試時的關(guān)鍵,尤其是在需要構(gòu)造大量測試用例時更是如此,因為如果完全依靠手工的方式來構(gòu)造和執(zhí)行這些測試,肯定會變成一個花費大量時間并且單調(diào)無味的工作,而測試框架則可以很好地解決這些問題。
單元測試的重要性就不多說了,可惡的是python中有太多的單元測試框架和工具,什么unittest, testtools, subunit, coverage, testrepository, nose, mox, mock, fixtures, discover,再加上setuptools, distutils等等這些,先不說如何寫單元測試,光是怎么運行單元測試就有N多種方法,再因為它是測試而非功能,是很多人沒興趣觸及的東西。但是作為一個優(yōu)秀的程序員,不僅要寫好功能代碼,寫好測試代碼一樣的彰顯你的實力。如此多的框架和工具,很容易讓人困惑,困惑的原因是因為并沒有理解它的基本原理,如果一些基本的概念都不清楚,怎么能夠?qū)懗鏊悸非逦臏y試代碼?
今天的主題就是unittest,作為標(biāo)準(zhǔn)python中的一個模塊,是其它框架和工具的基礎(chǔ),參考資料是它的官方文檔:http://docs.python.org/2.7/library/unittest.html和源代碼,文檔已經(jīng)寫的非常好了,我在這里記錄的主要是它的一些重要概念、關(guān)鍵點以及可能會碰到的一些坑,目的在于對unittest加深理解,而不是停留在泛泛的表面層上。
unittest是一個python版本的junit,junit是java中的單元測試框架,對java的單元測試,有一句話很貼切:Keep the bar green,相信使用eclipse寫過java單元測試的都心領(lǐng)神會。unittest實現(xiàn)了很多junit中的概念,比如我們非常熟悉的test case, test suite等,總之,原理都是相通的,只是用不同的語言表達(dá)出來。
在文檔的開篇就介紹了unittest中的4個重要的概念:test fixture, test case, test suite, test runner,我覺得只有理解了這幾個概念,才能真正的理解單元測試的基本原理,下面就主要圍繞這幾個概念來展開這篇文章。
首先通過查看unittest的源碼,來看一下這幾個概念,以及他們之間的關(guān)系,他們是如何在一起工作的,其靜態(tài)類圖如下:
一個TestCase的實例就是一個測試用例。什么是測試用例呢?就是一個完整的測試流程,包括測試前準(zhǔn)備環(huán)境的搭建(setUp),執(zhí)行測試代碼(run),以及測試后環(huán)境的還原(tearDown)。元測試(unit test)的本質(zhì)也就在這里,一個測試用例是一個完整的測試單元,通過運行這個測試單元,可以對某一個問題進(jìn)行驗證。
而多個測試用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。
TestLoader是用來加載TestCase到TestSuite中的,其中有幾個loadTestsFrom__()方法,就是從各個地方尋找TestCase,創(chuàng)建它們的實例,然后add到TestSuite中,再返回一個TestSuite實例。
TextTestRunner是來執(zhí)行測試用例的,其中的run(test)會執(zhí)行TestSuite/TestCase中的run(result)方法。
測試的結(jié)果會保存到TextTestResult實例中,包括運行了多少測試用例,成功了多少,失敗了多少等信息。
這樣整個流程就清楚了,首先是要寫好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來運行TestSuite,運行的結(jié)果保存在TextTestResult中,整個過程集成在unittest.main模塊中。
現(xiàn)在已經(jīng)涉及到了test case, test suite, test runner這三個概念了,還有test fixture沒有提到,那什么是test fixture呢??在TestCase的docstring中有這樣一段話:
Test authors should subclass TestCase for their own tests. Construction and deconstruction of the test's environment (‘fixture') can be implemented by overriding the ‘setUp' and ‘tearDown' methods respectively.
可見,對一個測試用例環(huán)境的搭建和銷毀,是一個fixture,通過覆蓋TestCase的setUp()和tearDown()方法來實現(xiàn)。這個有什么用呢?比如說在這個測試用例中需要訪問數(shù)據(jù)庫,那么可以在setUp()中建立數(shù)據(jù)庫連接以及進(jìn)行一些初始化,在tearDown()中清除在數(shù)據(jù)庫中產(chǎn)生的數(shù)據(jù),然后關(guān)閉連接。注意tearDown的過程很重要,要為以后的TestCase留下一個干凈的環(huán)境。關(guān)于fixture,還有一個專門的庫函數(shù)叫做fixtures,功能更加強(qiáng)大,以后會介紹到。
至此,概念和流程基本清楚了,下面通過簡單的例子再來實踐一下,就拿unittest文檔上的例子吧:
TestSequenceFunctions繼承自unittest.TestCase,重寫了setUp()方法,并且定義了三個以'test'開頭的方法,那這個TestSequenceFunctions類到底是個什么呢?它是一個測試用例,還是三個測試用例?說是三個測試用例的話,它本身繼承自TestCase,說是一個測試用例的話,里面又有三個test_*()方法,明顯是三個測試用例。其實,我們只要看一些TestLoader是如何加載測試用例的,就一清二楚了,在loader.TestLoader類中有一個loadTestsFromTestCase()方法:
getTestCaseNames()是從TestCase這個類中找所有以“test”開頭的方法,然后注意第9行,在構(gòu)造TestSuite對象時,其參數(shù)使用了一個map方法,即對testCaseNames中的每一個元素,使用testCaseClass為其構(gòu)造對象,其結(jié)果是一個TestCase的對象集合,可以用下面的代碼來分步說明:
可見,對每一個以test開頭的方法,都為其構(gòu)建了一個TestCase對象,值得注意的是,如果沒有定義test開頭的方法,而是將測試代碼寫到了一個名為runTest的方法中,那么會為該runTest方法構(gòu)建TestCase對象,如果定義了test開頭的方法,就會忽略runTest方法。
至此,基本就清楚了,每一個以test開頭的方法,都會為其構(gòu)建TestCase對象,也就是說TestSequenceFunctions類中其實定義了三個TestCase,之所以寫成這樣,是為了方便,因為這幾個測試用例的fixture是相同的,如果每一個測試用例單獨寫成一個TestCase的話,會有很多的冗余代碼。
明白了這些,文檔就可以很輕松的看懂了,至于怎么運行測試用例,以及其他的內(nèi)容,直接看文檔吧。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python錯誤NameError:name?'X'?is?not?defined的解決方法
這篇文章主要給大家介紹了關(guān)于Python錯誤NameError:name?‘X‘?is?not?defined的解決方法,這是最近工作中遇到的一個問題,文中通過實例代碼將解決的方法介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03