Python單元測試——深入理解unittest
單元測試的重要性就不多說了,可惡的是python中有太多的單元測試框架和工具,什麼unittest, testtools, subunit, coverage, testrepository, nose, mox, mock, fixtures, discover,再加上setuptools, distutils等等這些,先不說如何寫單元測試,光是怎麼執行單元測試就有N多種方法,再因為它是測試而非功能,是很多人沒興趣觸及的東西。但是作為一個優秀的程式設計師,不僅要寫好功能程式碼,寫好測試程式碼一樣的彰顯你的實力。如此多的框架和工具,很容易讓人困惑,困惑的原因是因為並沒有理解它的基本原理,如果一些基本的概念都不清楚,怎麼能夠寫出思路清晰的測試程式碼?
今天的主題就是unittest,作為標準python中的一個模組,是其它框架和工具的基礎,參考資料是它的官方文件:http://docs.python.org/2.7/library/unittest.html和原始碼,文件已經寫的非常好了,我在這裡記錄的主要是它的一些重要概念、關鍵點以及可能會碰到的一些坑,目的在於對unittest加深理解,而不是停留在泛泛的表面層上。
unittest是一個python版本的junit,junit是java中的單元測試框架,對java的單元測試,有一句話很貼切:Keep the bar green,相信使用eclipse寫過java單元測試的都心領神會。unittest實現了很多junit中的概念,比如我們非常熟悉的test case, test suite等,總之,原理都是相通的,只是用不同的語言表達出來。
在文件的開篇就介紹了unittest中的4個重要的概念:test fixture, test case, test suite, test runner,我覺得只有理解了這幾個概念,才能真正的理解單元測試的基本原理,下面就主要圍繞這幾個概念來展開這篇文章。
首先通過檢視unittest的原始碼,來看一下這幾個概念,以及他們之間的關係,他們是如何在一起工作的,其靜態類圖如下:
一個TestCase的例項就是一個測試用例。什麼是測試用例呢?就是一個完整的測試流程,包括測試前準備環境的搭建(setUp),執行測試程式碼(run),以及測試後環境的還原(tearDown)。元測試(unit test)的本質也就在這裡,一個測試用例是一個完整的測試單元,通過執行這個測試單元,可以對某一個問題進行驗證。
而多個測試用例集合在一起,就是TestSuite,而且TestSuite也可以巢狀TestSuite。
TestLoader是用來載入TestCase到TestSuite中的,其中有幾個loadTestsFrom__()方法,就是從各個地方尋找TestCase,建立它們的例項,然後add到TestSuite中,再返回一個TestSuite例項。
TextTestRunner是來執行測試用例的,其中的run(test)會執行TestSuite/TestCase中的run(result)方法。
測試的結果會儲存到TextTestResult例項中,包括運行了多少測試用例,成功了多少,失敗了多少等資訊。
這樣整個流程就清楚了,首先是要寫好TestCase,然後由TestLoader載入TestCase到TestSuite,然後由TextTestRunner來執行TestSuite,執行的結果儲存在TextTestResult中,整個過程整合在unittest.main模組中。
現在已經涉及到了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.
可見,對一個測試用例環境的搭建和銷燬,是一個fixture,通過覆蓋TestCase的setUp()和tearDown()方法來實現。這個有什麼用呢?比如說在這個測試用例中需要訪問資料庫,那麼可以在setUp()中建立資料庫連線以及進行一些初始化,在tearDown()中清除在資料庫中產生的資料,然後關閉連線。注意tearDown的過程很重要,要為以後的TestCase留下一個乾淨的環境。關於fixture,還有一個專門的庫函式叫做fixtures,功能更加強大,以後會介紹到。
至此,概念和流程基本清楚了,下面通過簡單的例子再來實踐一下,就拿unittest文件上的例子吧:
TestSequenceFunctions繼承自unittest.TestCase,重寫了setUp()方法,並且定義了三個以’test’開頭的方法,那這個TestSequenceFunctions類到底是個什麼呢?它是一個測試用例,還是三個測試用例?說是三個測試用例的話,它本身繼承自TestCase,說是一個測試用例的話,裡面又有三個test_*()方法,明顯是三個測試用例。其實,我們只要看一些TestLoader是如何載入測試用例的,就一清二楚了,在loader.TestLoader類中有一個loadTestsFromTestCase()方法:
getTestCaseNames()是從TestCase這個類中找所有以“test”開頭的方法,然後注意第9行,在構造TestSuite物件時,其引數使用了一個map方法,即對testCaseNames中的每一個元素,使用testCaseClass為其構造物件,其結果是一個TestCase的物件集合,可以用下面的程式碼來分步說明:
可見,對每一個以test開頭的方法,都為其構建了一個TestCase物件,值得注意的是,如果沒有定義test開頭的方法,而是將測試程式碼寫到了一個名為runTest的方法中,那麼會為該runTest方法構建TestCase物件,如果定義了test開頭的方法,就會忽略runTest方法。
至此,基本就清楚了,每一個以test開頭的方法,都會為其構建TestCase物件,也就是說TestSequenceFunctions類中其實定義了三個TestCase,之所以寫成這樣,是為了方便,因為這幾個測試用例的fixture是相同的,如果每一個測試用例單獨寫成一個TestCase的話,會有很多的冗餘程式碼。
明白了這些,文件就可以很輕鬆的看懂了,至於怎麼執行測試用例,以及其他的內容,直接看文件吧。
歡迎大家加群:599411652 或者加本人qq:2327243380