聊聊 Python 的單元測試框架(二):nose 和它的繼任者 nose2
作者:HelloGitHub-Prodesire
HelloGitHub 的《講解開源專案》系列,專案地址:https://github.com/HelloGitHub-Team/Article
一、nose
nose 是一個第三方單元測試框架,它完全相容 unittest
,並且號稱是一個更好用的測試框架。
那麼 nose
除了具備 unittest
的所有功能外,還具有哪些優勢呢?
1.1 用例編寫
用例的編寫方式除了編寫繼承於 unittest.TestCase 的測試類外,還可以編寫成沒有繼承的測試類。比如,寫成如下形式也會被 nose
視作一個測試類:
from nose.tools import raises class TestStringMethods: def test_upper(self): assert 'foo'.upper() == 'FOO' def test_isupper(self): assert 'FOO'.isupper() assert not 'Foo'.isupper() @raises(TypeError) def test_split(self): s = 'hello world' assert s.split() == ['hello', 'world'] # check that s.split fails when the separator is not a string s.split(2)
當然,測試類並沒有繼承 unittest.TestCase
,將不能使用其內建的各類 assertXXX
方法,進而導致用例出錯時無法獲得更加詳細的上下文資訊。
此外,nose
也支援定義函式來作為測試,這給許多簡單的測試場景帶來很大的便利:
def test_upper():
assert 'foo'.upper() == 'FOO'
1.2 用例發現和執行
unittest
所支援的用例發現和執行能力,nose
均支援。
nose
支援用例自動(遞迴)發現:
- 預設發現當前目錄下所有包含
test
的測試用例,但不包括以_
開頭的用例- 使用
nosetests
命令
- 使用
- 通過
-w
-m
引數指定用例檔案、目錄、函式、類的名稱模式(正則匹配)nosetests -w project_directory "test_.+"
nose
也支援執行指定用例:
- 指定測試模組
nosetests test.module
- 指定測試類
nosetests a.test:TestCase
- 指定測試方法
nosetests another.test:TestCase.test_method
- 指定測試檔案路徑
nosetests /path/to/test/file.py
- 指定測試檔案路徑+測試類或測試函式(這是
unittest
所不支援的)nosetests /path/to/test/file.py:TestCase
nosetests /path/to/test/file.py:TestCase.test_method
nosetests /path/to/test/file.py:test_function
1.3 測試夾具(Fixtures)
nose
除了支援 unittest
所支援的定義測試前置和清理方式,還支援一種更為簡單的定義方式:
def setup_func():
"set up test fixtures"
def teardown_func():
"tear down test fixtures"
@with_setup(setup_func, teardown_func)
def test():
"test ..."
只需定義兩個函式用來表示前置和清理方法,通過 nose.tools.with_setup 裝飾器裝飾測試函式,nose
便會在執行測試用例前後分別執行所定義的前置和清理函式。
1.4 子測試/測試生成器
nose
除了支援 unittest
中的 TestCase.subTest
,還支援一種更為強大的子測試編寫方式,也就是 測試生成器(Test generators)
,通過 yield
實現。
在下面的示例中,定義一個 test_evens
測試函式,裡面生成了 5 個子測試 check_even
:
def test_evens():
for i in range(0, 5):
yield check_even, i, i*3
def check_even(n, nn):
assert n % 2 == 0 or nn % 2 == 0
此外,相較於 unittest.TestCase.subTest
多個子測試只能執行一次測試前置和清理,nose
的 測試生成器
可以支援每個子測試執行一次測試前置和清理,如:
def test_generator():
# ...
yield func, arg, arg # ...
@with_setup(setup_func, teardown_func)
def func(arg):
assert something_about(arg)
1.5 外掛體系
nose
相較於 unittest
一個最大的優勢就是外掛體系,自帶了很多有用的外掛,也有豐富的第三方外掛。這樣就能做更多的事情。
其中,自帶外掛如下:
- AllModules:在所有模組中收集用例
- Attrib:給用例打標籤,並可執行含指定標籤的用例
- Capture:捕獲用例的標準輸出
- Collect:快速收集用例
- Cover:統計程式碼覆蓋率
- Debug:用例失敗時進入 pdb 除錯
- Deprecated:標記用例為棄用
- Doctests:執行文件用例
- Failure Detail:斷言失敗時提供上下文資訊
- Isolate:保護用例避免受一些副作用的影響
- Logcapture:捕捉 logging 輸出
- Multiprocess:並行執行用例
- Prof:使用熱點分析器進行分析
- Skip:標記用例為跳過
- Testid:為輸出的每個用例名稱新增測試 ID
- Xunit:以 xunit 格式輸出測試結果
而第三方庫則多種多樣,如用來生成 HTML 格式測試報告的 nose-htmloutput 等,這裡不再一一列出。
得益於 nose
豐富的外掛生態,當 nose
本身不能夠完全滿足我們的測試需求時,可以通過安裝外掛,並在 nosetests
命令列指定該外掛所提供的特定引數即可非常容易的使用外掛。
相較於 unittest
,就能省去很多自己開發額外測試邏輯的精力。
二、nose2
nose2 是 nose 的繼任者。
它們的理念都是讓編寫和執行測試用例變得更容易。
它們有很多相同點,比如都相容 unittest
,支援使用函式作為測試用例,支援子測試,擁有外掛體系。但也有很多不同點,下面列出一些主要的不同點:
- 發現和載入測試
nose
自行實現了模組載入功能,使用惰性方式載入測試模組,載入一個執行一個。nose2
則藉助內建的 import() 匯入模組,並且是先全部載入,再執行用例nose2
並不支援nose
所支援的所有測試用例專案結構,比如如下用例檔案的結構在nose2
中就不受支援:
.
`-- tests
|-- more_tests
| `-- test.py
`-- test.py
- 測試前置和清理函式級別
nose
支援方法、類、模組和包級別的測試前置和清理函式nose2
則不支援包級別的測試前置和清理函式
- 子測試
nose2
除了支援使用測試生成器來實現子測試外,還支援使用引數化測試(Parameterized tests)來實現子測試nose2
除了像nose
一樣支援在測試函式和測試類(不繼承於unittest.TestCase
)中支援引數化測試和測試生成器外,還支援在繼承於unittest.TestCase
的測試類中使用
- 配置化
nose
期望所有外掛的配置通過命令列引數進行配置nose2
則通過配置檔案進行控制,以最小化命令列引數讓人讀得更舒服
更多對比詳見 官方文件。
三、小結
nose
和 nose2
在做到相容 unittest
上就足以看出它們的目標,那便是要吸引原來那些使用 unittest
的使用者來使用它們。它們確實做到了!
nose
和 nose2
在用例編寫、測試夾具、子測試上做出改進,已經能讓日常用例編寫工作變得更加容易和靈活。同時又引入外掛體系,進一步將單元測試框架的能力提升了一個大大的臺階,這讓很多在基礎測試功能之上的高階功能的實現和共享成為了可能。也難怪有眾多開發者對它們情有獨鍾。
『講解開源專案系列』——讓對開源專案感興趣的人不再畏懼、讓開源專案的發起者不再孤單。跟著我們的文章,你會發現程式設計的樂趣、使用和發現參與開源專案如此簡單。歡迎留言聯絡我們、加入我們,讓更多人愛上開源、貢獻開源