python开源考试_可能是 Python 中最火的第三方开源测试框架 pytest
作者:HelloGitHub-Prodesire
一、介紹
本篇文章是《聊聊 Python 的單元測(cè)試框架》的第三篇,前兩篇分別介紹了標(biāo)準(zhǔn)庫(kù) unittest 和第三方單元測(cè)試框架 nose。作為本系列的最后一篇,壓軸出場(chǎng)的是Python 世界中最火的第三方單元測(cè)試框架:pytest。
它有如下主要特性:
assert 斷言失敗時(shí)輸出詳細(xì)信息(再也不用去記憶 self.assert* 名稱了)
自動(dòng)發(fā)現(xiàn) 測(cè)試模塊和函數(shù)
模塊化夾具 用以管理各類測(cè)試資源
對(duì) unittest 完全兼容,對(duì) nose 基本兼容
非常豐富的插件體系,有超過 315 款第三方插件,社區(qū)繁榮
和前面介紹 unittest 和 nose 一樣,我們將從如下幾個(gè)方面介紹 pytest 的特性。
二、用例編寫
同 nose 一樣,pytest 支持函數(shù)、測(cè)試類形式的測(cè)試用例。最大的不同點(diǎn)是,你可以盡情地使用 assert 語句進(jìn)行斷言,絲毫不用擔(dān)心它會(huì)在 nose 或 unittest 中產(chǎn)生的缺失詳細(xì)上下文信息的問題。
比如下面的測(cè)試示例中,故意使得 test_upper 中斷言不通過:
import pytest
def test_upper():
assert 'foo'.upper() == 'FOO1'
class TestClass:
def test_one(self):
x = "this"
assert "h" in x
def test_two(self):
x = "hello"
with pytest.raises(TypeError):
x + []
而當(dāng)使用 pytest 去執(zhí)行用例時(shí),它會(huì)輸出詳細(xì)的(且是多種顏色)上下文信息:
=================================== test session starts ===================================
platform darwin -- Python 3.7.1, pytest-4.0.1, py-1.7.0, pluggy-0.8.0
rootdir: /Users/prodesire/projects/tests, inifile:
plugins: cov-2.6.0
collected 3 items
test.py F.. [100%]
======================================== FAILURES =========================================
_______________________________________ test_upper ________________________________________
def test_upper():
> assert 'foo'.upper() == 'FOO1'
E AssertionError: assert 'FOO' == 'FOO1'
E - FOO
E + FOO1
E ? +
test.py:4: AssertionError
=========================== 1 failed, 2 passed in 0.08 seconds ============================
不難看到,pytest 既輸出了測(cè)試代碼上下文,也輸出了被測(cè)變量值的信息。相比于 nose 和 unittest,pytest 允許用戶使用更簡(jiǎn)單的方式編寫測(cè)試用例,又能得到一個(gè)更豐富和友好的測(cè)試結(jié)果。
三、用例發(fā)現(xiàn)和執(zhí)行
unittest 和 nose 所支持的用例發(fā)現(xiàn)和執(zhí)行能力,pytest 均支持。
pytest 支持用例自動(dòng)(遞歸)發(fā)現(xiàn):
默認(rèn)發(fā)現(xiàn)當(dāng)前目錄下所有符合 test_*.py 或 *_test.py 的測(cè)試用例文件中,以 test 開頭的測(cè)試函數(shù)或以 Test 開頭的測(cè)試類中的以 test 開頭的測(cè)試方法
使用 pytest 命令
同 nose2 的理念一樣,通過在配置文件中指定特定參數(shù),可配置用例文件、類和函數(shù)的名稱模式(模糊匹配)
pytest 也支持執(zhí)行指定用例:
指定測(cè)試文件路徑
pytest /path/to/test/file.py
指定測(cè)試類
pytest /path/to/test/file.py:TestCase
指定測(cè)試方法
pytest another.test::TestClass::test_method
指定測(cè)試函數(shù)
pytest /path/to/test/file.py:test_function
四、測(cè)試夾具(Fixtures)
pytest 的測(cè)試夾具和 unittest、nose、nose2的風(fēng)格迥異,它不但能實(shí)現(xiàn) setUp 和 tearDown這種測(cè)試前置和清理邏輯,還其他非常多強(qiáng)大的功能。
4.1 聲明和使用
pytest 中的測(cè)試夾具更像是測(cè)試資源,你只需定義一個(gè)夾具,然后就可以在用例中直接使用它。得益于 pytest 的依賴注入機(jī)制,你無需通過from xx import xx的形式顯示導(dǎo)入,只需要在測(cè)試函數(shù)的參數(shù)中指定同名參數(shù)即可,比如:
import pytest
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
上述示例中定義了一個(gè)測(cè)試夾具 smtp_connection,在測(cè)試函數(shù) test_ehlo 簽名中定義了同名參數(shù),則 pytest 框架會(huì)自動(dòng)注入該變量。
4.2 共享
在 pytest 中,同一個(gè)測(cè)試夾具可被多個(gè)測(cè)試文件中的多個(gè)測(cè)試用例共享。只需在包(Package)中定義 conftest.py 文件,并把測(cè)試夾具的定義寫在該文件中,則該包內(nèi)所有模塊(Module)的所有測(cè)試用例均可使用 conftest.py 中所定義的測(cè)試夾具。
比如,如果在如下文件結(jié)構(gòu)的 test_1/conftest.py 定義了測(cè)試夾具,那么 test_a.py 和 test_b.py 可以使用該測(cè)試夾具;而 test_c.py 則無法使用。
`-- test_1
| |-- conftest.py
| `-- test_a.py
| `-- test_b.py
`-- test_2
`-- test_c.py
4.3 生效級(jí)別
unittest 和 nose 均支持測(cè)試前置和清理的生效級(jí)別:測(cè)試方法、測(cè)試類和測(cè)試模塊。
pytest 的測(cè)試夾具同樣支持各類生效級(jí)別,且更加豐富。通過在 pytest.fixture 中指定 scope 參數(shù)來設(shè)置:
function —— 函數(shù)級(jí),即調(diào)用每個(gè)測(cè)試函數(shù)前,均會(huì)重新生成 fixture
class —— 類級(jí),調(diào)用每個(gè)測(cè)試類前,均會(huì)重新生成 fixture
module —— 模塊級(jí),載入每個(gè)測(cè)試模塊前,均會(huì)重新生成 fixture
package —— 包級(jí),載入每個(gè)包前,均會(huì)重新生成 fixture
session —— 會(huì)話級(jí),運(yùn)行所有用例前,只生成一次 fixture
當(dāng)我們指定生效級(jí)別為模塊級(jí)時(shí),示例如下:
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
4.4 測(cè)試前置和清理
pytest 的測(cè)試夾具也能夠?qū)崿F(xiàn)測(cè)試前置和清理,通過 yield 語句來拆分這兩個(gè)邏輯,寫法變得很簡(jiǎn)單,如:
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp_connection # provide the fixture value
print("teardown smtp")
smtp_connection.close()
在上述示例中,yield smtp_connection 及前面的語句相當(dāng)于測(cè)試前置,通過 yield 返回準(zhǔn)備好的測(cè)試資源 smtp_connection;
而后面的語句則會(huì)在用例執(zhí)行結(jié)束(確切的說是測(cè)試夾具的生效級(jí)別的聲明周期結(jié)束時(shí))后執(zhí)行,相當(dāng)于測(cè)試清理。
如果生成測(cè)試資源(如示例中的 smtp_connection)的過程支持 with 語句,那么還可以寫成更加簡(jiǎn)單的形式:
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
五、跳過測(cè)試和預(yù)計(jì)失敗
pytest 除了支持 unittest 和 nosetest 的跳過測(cè)試和預(yù)計(jì)失敗的方式外,還在 pytest.mark 中提供對(duì)應(yīng)方法:
通過 skipif按條件跳過測(cè)試
通過 xfail 預(yù)計(jì)測(cè)試失敗
示例如下:
@pytest.mark.skip(reason="no way of currently testing this")
def test_mark_skip():
...
def test_skip():
if not valid_config():
pytest.skip("unsupported configuration")
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
def test_mark_skip_if():
...
@pytest.mark.xfail
def test_mark_xfail():
...
六、子測(cè)試/參數(shù)化測(cè)試
pytest 除了支持 unittest 中的 TestCase.subTest,還支持一種更為靈活的子測(cè)試編寫方式,也就是 參數(shù)化測(cè)試,通過 pytest.mark.parametrize 裝飾器實(shí)現(xiàn)。
在下面的示例中,定義一個(gè) test_eval 測(cè)試函數(shù),通過 pytest.mark.parametrize 裝飾器指定 3 組參數(shù),則將生成 3 個(gè)子測(cè)試:
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expected
示例中故意讓最后一組參數(shù)導(dǎo)致失敗,運(yùn)行用例可以看到豐富的測(cè)試結(jié)果輸出:
========================================= test session starts =========================================
platform darwin -- Python 3.7.1, pytest-4.0.1, py-1.7.0, pluggy-0.8.0
rootdir: /Users/prodesire/projects/tests, inifile:
plugins: cov-2.6.0
collected 3 items
test.py ..F [100%]
============================================== FAILURES ===============================================
__________________________________________ test_eval[6*9-42] __________________________________________
test_input = '6*9', expected = 42
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
> assert eval(test_input) == expected
E AssertionError: assert 54 == 42
E + where 54 = eval('6*9')
test.py:6: AssertionError
================================= 1 failed, 2 passed in 0.09 seconds ==================================
若將參數(shù)換成 pytest.param,我們還可以有更高階的玩法,比如知道最后一組參數(shù)是失敗的,所以將它標(biāo)記為 xfail:
@pytest.mark.parametrize(
"test_input,expected",
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
如果測(cè)試函數(shù)的多個(gè)參數(shù)的值希望互相排列組合,我們可以這么寫:
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
pass
上述示例中會(huì)分別把 x=0/y=2、x=1/y=2、x=0/y=3和x=1/y=3帶入測(cè)試函數(shù),視作四個(gè)測(cè)試用例來執(zhí)行。
七、測(cè)試結(jié)果輸出
pytest 的測(cè)試結(jié)果輸出相比于 unittest 和 nose 來說更為豐富,其優(yōu)勢(shì)在于:
高亮輸出,通過或不通過會(huì)用不同的顏色進(jìn)行區(qū)分
更豐富的上下文信息,自動(dòng)輸出代碼上下文和變量信息
測(cè)試進(jìn)度展示
測(cè)試結(jié)果輸出布局更加友好易讀
八、插件體系
pytest 的插件十分豐富,而且即插即用,作為使用者不需要編寫額外代碼。關(guān)于插件的使用,參見"Installing and Using plugins"。
此外,得益于 pytest 良好的架構(gòu)設(shè)計(jì)和鉤子機(jī)制,其插件編寫也變得容易上手。關(guān)于插件的編寫,參見"Writing plugins"。
九、總結(jié)
三篇關(guān)于 Python 測(cè)試框架的介紹到這里就要收尾了。寫了這么多,各位看官怕也是看得累了。我們不妨羅列一個(gè)橫向?qū)Ρ缺?#xff0c;來總結(jié)下這些單元測(cè)試框架的異同:
unittest
nose
nose2
pytest
自動(dòng)發(fā)現(xiàn)用例
?
?
?
?
指定(各級(jí)別)用例執(zhí)行
?
?
?
?
支持 assert 斷言
弱
弱
弱
強(qiáng)
測(cè)試夾具
?
?
?
?
測(cè)試夾具種類
前置和清理
前置和清理
前置和清理
前置、清理、內(nèi)置各類 fixtures,自定義各類 fixtures
測(cè)試夾具生效級(jí)別
方法、類、模塊
方法、類、模塊
方法、類、模塊
方法、類、模塊、包、會(huì)話
支持跳過測(cè)試和預(yù)計(jì)失敗
?
?
?
?
子測(cè)試
?
?
?
?
測(cè)試結(jié)果輸出
一般
較好
較好
好
插件
-
較豐富
一般
豐富
鉤子
-
-
?
?
社區(qū)生態(tài)
作為標(biāo)準(zhǔn)庫(kù),由官方維護(hù)
停止維護(hù)
維護(hù)中,活躍度低
維護(hù)中,活躍度高
Python 的單元測(cè)試框架看似種類繁多,實(shí)則是一代代的進(jìn)化,有跡可循。抓住其特點(diǎn),結(jié)合使用場(chǎng)景,就能容易的做出選擇。
若你不想安裝或不允許第三方庫(kù),那么 unittest 是最好也是唯一的選擇。反之,pytest 無疑是最佳選擇,眾多 Python 開源項(xiàng)目(如大名鼎鼎的 requests)都是使用 pytest 作為單元測(cè)試框架。甚至,連 nose2 在官方文檔上都建議大家使用 pytest,這得是多大的敬佩呀!
『講解開源項(xiàng)目系列』——讓對(duì)開源項(xiàng)目感興趣的人不再畏懼、讓開源項(xiàng)目的發(fā)起者不再孤單。跟著我們的文章,你會(huì)發(fā)現(xiàn)編程的樂趣、使用和發(fā)現(xiàn)參與開源項(xiàng)目如此簡(jiǎn)單。歡迎留言聯(lián)系我們、加入我們,讓更多人愛上開源、貢獻(xiàn)開源~
總結(jié)
以上是生活随笔為你收集整理的python开源考试_可能是 Python 中最火的第三方开源测试框架 pytest的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python实现邮件客户端_SMTP邮件
- 下一篇: python2处理耗时任务_Rabbit