目錄:
- 安裝及入門
- 使用和調(diào)用方法
- 原有TestSuite使用方法
- 斷言的編寫和報(bào)告
- Pytest fixtures:清晰 模塊化 易擴(kuò)展
- 使用Marks標(biāo)記測試用例
- Monkeypatching/對模塊和環(huán)境進(jìn)行Mock
- 使用tmp目錄和文件
- 捕獲stdout及stderr輸出
- 捕獲警告信息
- 模塊及測試文件中集成doctest測試
- skip及xfail: 處理不能成功的測試用例
- Fixture方法及測試用例的參數(shù)化
- 緩存: 使用跨執(zhí)行狀態(tài)
- unittest.TestCase支持
- 運(yùn)行Nose用例
- 經(jīng)典xUnit風(fēng)格的setup/teardown
- 安裝和使用插件
- 插件編寫
- 編寫鉤子(hook)方法
- 運(yùn)行日志
- API參考
- 優(yōu)質(zhì)集成實(shí)踐
- 片狀測試
- Pytest導(dǎo)入機(jī)制及sys.path/PYTHONPATH
- 配置選項(xiàng)
- 示例及自定義技巧
- Bash自動補(bǔ)全設(shè)置
Pytest fixtures:清晰 模塊化 易擴(kuò)展
2.0/2.3/2.4版本新功能
text fixtures的目的是為測試的重復(fù)執(zhí)行提供一個(gè)可靠的固定基線淡喜。 pytest fixture比經(jīng)典的xUnit setUp/tearDown方法有著顯著的改進(jìn):
- fixtures具有明確的名稱稽鞭,在測試方法/類/模塊或整個(gè)項(xiàng)目中通過聲明使用的fixtures名稱來使用齐苛。
- fixtures以模塊化方式實(shí)現(xiàn),因?yàn)槊總€(gè)fixture名稱都會觸發(fā)調(diào)用fixture函數(shù)胸嘁,該fixture函數(shù)本身可以使用其它的fixtures瓶摆。
- 從簡單的單元測試到復(fù)雜的功能測試,fixtures的管理允許根據(jù)配置和組件選項(xiàng)對fixtures和測試用例進(jìn)行參數(shù)化性宏,或者在測試方法/類/模塊或整個(gè)測試會話范圍內(nèi)重復(fù)使用該fixture群井。
此外,pytest繼續(xù)支持經(jīng)典的xUnit風(fēng)格的setup方法毫胜。 你可以根據(jù)需要混合使用兩種樣式书斜,逐步從經(jīng)典樣式移動到新樣式。 您也可以從現(xiàn)有的unittest.TestCase樣式或基于nose的項(xiàng)目開始酵使。
Fixtures作為函數(shù)參數(shù)使用
測試方法可以通過在其參數(shù)中使用fixtures名稱來接收fixture對象荐吉。 每個(gè)fixture參數(shù)名稱所對應(yīng)的函數(shù),可以通過使用@pytest.fixture
注冊成為一個(gè)fixture函數(shù)口渔,來為測試方法提供一個(gè)fixture對象样屠。 讓我們看一個(gè)只包含一個(gè)fixture和一個(gè)使用它的測試方法的簡單獨(dú)立測試模塊:
# ./test_smtpsimple.py內(nèi)容
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
assert 0 # for demo purposes
這里,test_ehlo
需要smtp_connection
來提供fixture對象缺脉。pytest將發(fā)現(xiàn)并調(diào)用帶@pytest.fixture
裝飾器的smtp_connection
fixture函數(shù)瞧哟。 運(yùn)行測試如下所示:
$ pytest test_smtpsimple.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_smtpsimple.py F [100%]
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_smtpsimple.py:11: AssertionError
========================= 1 failed in 0.12 seconds =========================
在測試失敗的回溯信息中,我們看到測試方法是使用smtp_connection
參數(shù)調(diào)用的枪向,即由fixture函數(shù)創(chuàng)建的smtplib.SMTP()
實(shí)例。測試用例在我們故意的assert 0
上失敗咧党。以下是pytest用這種方式調(diào)用測試方法使用的確切協(xié)議:
Fixtures: 依賴注入的主要例子
Fixtures允許測試方法能輕松引入預(yù)先定義好的初始化準(zhǔn)備函數(shù)秘蛔,而無需關(guān)心導(dǎo)入/設(shè)置/清理方法的細(xì)節(jié)。 這是依賴注入的一個(gè)主要示例,其中fixture函數(shù)的功能扮演”注入器“的角色深员,測試方法來“消費(fèi)”這些fixture對象负蠕。
conftest.py: 共享fixture函數(shù)
如果在測試中需要使用多個(gè)測試文件中的fixture函數(shù),則可以將其移動到conftest.py
文件中倦畅,所需的fixture對象會自動被pytest
發(fā)現(xiàn)遮糖,而不需要再每次導(dǎo)入。 fixture函數(shù)的發(fā)現(xiàn)順序從測試類開始叠赐,然后是測試模塊欲账,然后是conftest.py文件,最后是內(nèi)置和第三方插件芭概。
你還可以使用conftest.py文件來實(shí)現(xiàn)本地每個(gè)目錄的插件赛不。
共享測試數(shù)據(jù)
如果要使用數(shù)據(jù)文件中的測試數(shù)據(jù),最好的方法是將這些數(shù)據(jù)加載到fixture函數(shù)中以供測試方法注入使用罢洲。這利用到了pytest
的自動緩存機(jī)制踢故。
另一個(gè)好方法是在tests文件夾中添加數(shù)據(jù)文件初澎。 還有社區(qū)插件可用于幫助處理這方面的測試炕泳,例如:pytest-datadir
和pytest-datafiles
畏腕。
生效范圍:在測試類/測試模塊/測試會話中共享fixture對象
由于fixtures對象需要連接形成依賴網(wǎng)赊堪,而通常創(chuàng)建時(shí)間比較長贿条。 擴(kuò)展前面的示例冯袍,我們可以在@pytest.fixture
調(diào)用中添加scope ="module"
參數(shù)啄巧,以使每個(gè)測試模塊只調(diào)用一次修飾的smtp_connection
fixture函數(shù)(默認(rèn)情況下苗踪,每個(gè)測試函數(shù)調(diào)用一次)触机。 因此帚戳,測試模塊中的多個(gè)測試方法將各自注入相同的smtp_connection
fixture對象,從而節(jié)省時(shí)間儡首。scope參數(shù)的可選值包括:function(函數(shù)), class(類), module(模塊), package(包)及 session(會話)片任。
下一個(gè)示例將fixture函數(shù)放入單獨(dú)的conftest.py文件中,以便來自目錄中多個(gè)測試模塊的測試可以訪問fixture函數(shù):
# conftest.py文件內(nèi)容
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
fixture對象的名稱依然是smtp_connection
蔬胯,你可以通過在任何測試方法或fixture函數(shù)(在conftest.py所在的目錄中或下面)使用參數(shù)smtp_connection
作為輸入?yún)?shù)來訪問其結(jié)果:
# test_module.py文件內(nèi)容
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
assert 0 # for demo purposes
我們故意插入失敗的assert 0
語句对供,以便檢查發(fā)生了什么,運(yùn)行測試并查看結(jié)果:
$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py FF [100%]
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
> assert 0 # for demo purposes
E assert 0
test_module.py:6: AssertionError
________________________________ test_noop _________________________________
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_module.py:11: AssertionError
========================= 2 failed in 0.12 seconds =========================
你會看到兩個(gè)assert 0
失敗信息氛濒,更重要的是你還可以看到相同的(模塊范圍的)smtp_connection
對象被傳遞到兩個(gè)測試方法中产场,因?yàn)閜ytest在回溯信息中顯示傳入的參數(shù)值。 因此舞竿,使用smtp_connection
的兩個(gè)測試方法運(yùn)行速度與單個(gè)函數(shù)一樣快京景,因?yàn)樗鼈冎赜昧讼嗤膄ixture對象。
如果您決定要使用session(會話骗奖,一次運(yùn)行算一次會話)范圍的smtp_connection
對象确徙,則只需如下聲明:
@pytest.fixture(scope="session")
def smtp_connection():
# the returned fixture value will be shared for
# all tests needing it
...
最后醒串,class(類)范圍將為每個(gè)測試類調(diào)用一次fixture對象。
注意:
Pytest一次只會緩存一個(gè)fixture實(shí)例鄙皇。 這意味著當(dāng)使用參數(shù)化fixture時(shí)芜赌,pytest可能會在給定范圍內(nèi)多次調(diào)用fixture函數(shù)。
package(包)范圍的fixture(實(shí)驗(yàn)性功能)
3.7版本新功能
在pytest 3.7中伴逸,引入了包范圍缠沈。 當(dāng)包的最后一次測試結(jié)束時(shí),最終確定包范圍的fixture函數(shù)错蝴。
警告:
此功能是實(shí)驗(yàn)性的洲愤,如果在獲得更多使用后發(fā)現(xiàn)隱藏的角落情況或此功能的嚴(yán)重問題,可能會在將來的版本中刪除漱竖。
謹(jǐn)慎使用此新功能禽篱,請務(wù)必報(bào)告您發(fā)現(xiàn)的任何問題。
高范圍的fixture函數(shù)優(yōu)先實(shí)例化
3.5版本新功能
在測試函數(shù)的fixture對象請求中馍惹,較高范圍的fixture(例如session會話級)較低范圍的fixture(例如function函數(shù)級或class類級優(yōu)先執(zhí)行躺率。相同范圍的fixture對象的按引入的順序及fixtures之間的依賴關(guān)系按順序調(diào)用。
請考慮以下代碼:
@pytest.fixture(scope="session")
def s1():
pass
@pytest.fixture(scope="module")
def m1():
pass
@pytest.fixture
def f1(tmpdir):
pass
@pytest.fixture
def f2():
pass
def test_foo(f1, m1, f2, s1):
...
test_foo
中fixtures將按以下順序執(zhí)行:
- s1:是最高范圍的fixture(會話級)
- m1:是第二高的fixture(模塊級)
- tmpdir:是一個(gè)函數(shù)級的fixture万矾,f1依賴它悼吱,因此它需要在f1前調(diào)用
- f1:是test_foo參數(shù)列表中第一個(gè)函數(shù)范圍的fixture。
- f2:是test_foo參數(shù)列表中最后一個(gè)函數(shù)范圍的fixture良狈。
fixture結(jié)束/執(zhí)行teardown代碼
當(dāng)fixture超出范圍時(shí)后添,通過使用yield語句而不是return,pytest支持fixture執(zhí)行特定的teardown代碼薪丁。yield語句之后的所有代碼都視為teardown代碼:
# conftest.py文件內(nèi)容
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()
無論測試的異常狀態(tài)如何,print
和smtp.close()
語句將在模塊中的最后一個(gè)測試完成執(zhí)行時(shí)執(zhí)行遇西。
讓我們執(zhí)行一下(上文的test_module.py
):
$ pytest -s -q --tb=no
FFteardown smtp
2 failed in 0.12 seconds
我們看到smtp_connection
實(shí)例在兩個(gè)測試完成執(zhí)行后完成。 請注意严嗜,如果我們使用scope ='function'
修飾我們的fixture函數(shù)粱檀,那么每次單個(gè)測試都會進(jìn)行fixture的setup和teardown。 在任何一種情況下漫玄,測試模塊本身都不需要改變或了解fixture函數(shù)的這些細(xì)節(jié)茄蚯。
請注意,我們還可以使用with語句無縫地使用yield語法:
# test_yield2.py文件內(nèi)容
import smtplib
import pytest
@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
測試結(jié)束后, smtp_connection
連接將關(guān)閉睦优,因?yàn)楫?dāng)with語句結(jié)束時(shí)渗常,smtp_connection
對象會自動關(guān)閉。
請注意汗盘,如果在設(shè)置代碼期間(yield關(guān)鍵字之前)發(fā)生異常皱碘,則不會調(diào)用teardown代碼(在yield之后)。
執(zhí)行teardown代碼的另一種選擇是利用請求上下文對象的addfinalizer
方法來注冊teardown函數(shù)隐孽。
以下是smtp_connection
fixture函數(shù)更改為使用addfinalizer
進(jìn)行teardown:
# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection(request):
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def fin():
print("teardown smtp_connection")
smtp_connection.close()
request.addfinalizer(fin)
return smtp_connection # provide the fixture value
yield
和addfinalizer
方法在測試結(jié)束后調(diào)用它們的代碼時(shí)的工作方式類似尸执,但addfinalizer
相比yield
有兩個(gè)主要區(qū)別:
- 使用
addfinalizer
可以注冊多個(gè)teardown功能家凯。 - 無論fixture中setup代碼是否引發(fā)異常,都將始終調(diào)用teardown代碼如失。 即使其中一個(gè)資源無法創(chuàng)建/獲取,也可以正確關(guān)閉fixture函數(shù)創(chuàng)建的所有資源:
@pytest.fixture
def equipments(request):
r = []
for port in ('C1', 'C3', 'C28'):
equip = connect(port)
request.addfinalizer(equip.disconnect)
r.append(equip)
return r
在上面的示例中送粱,如果“C28”因異常而失敗褪贵,則“C1”和“C3”仍將正確關(guān)閉。 當(dāng)然抗俄,如果在注冊finalize
函數(shù)之前發(fā)生異常脆丁,那么它將不會被執(zhí)行。
Fixtures中使用測試上下文的內(nèi)省信息
Fixtures工廠方法
Fixtures參數(shù)化
使用參數(shù)化fixtures標(biāo)記
模塊化:在fixture函數(shù)中使用fixtures功能
使用fixture實(shí)例自動組織測試用例
在類/模塊/項(xiàng)目中使用fixtures
自動使用fixtures(xUnit 框架的setup固定方法)
不同級別的fixtures的覆蓋(優(yōu)先級)
相對于在較大范圍的測試套件中的Test Fixtures方法动雹,在較小范圍子套件你可能需要重寫和覆蓋外層的Test Fixtures方法槽卫,從而保持測試代碼的可讀性和可維護(hù)性。
在文件夾級別(通過conftest文件)重寫fixtures方法
假設(shè)用例目錄結(jié)構(gòu)為:
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
test_something.py
# content of tests/test_something.py
def test_username(username):
assert username == 'username'
subfolder/
__init__.py
conftest.py
# content of tests/subfolder/conftest.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
test_something.py
# content of tests/subfolder/test_something.py
def test_username(username):
assert username == 'overridden-username'
你可以看到, 基礎(chǔ)/上級fixtures方法可以通過子文件夾下的con
ftest.py中同名的fixtures方法覆蓋, 非常簡單, 只需要按照上面的例子使用即可.
在測試模塊級別重寫fixtures方法
假設(shè)用例文件結(jié)構(gòu)如下:
tests/
__init__.py
conftest.py
# content of tests/conftest.py
@pytest.fixture
def username():
return 'username'
test_something.py
# content of tests/test_something.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
def test_username(username):
assert username == 'overridden-username'
test_something_else.py
# content of tests/test_something_else.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-else-' + username
def test_username(username):
assert username == 'overridden-else-username'
上面的例子中, 用例模塊(文件)中的fixture方法會覆蓋文件夾conftest.py中同名的fixtures方法
在直接參數(shù)化方法中覆蓋fixtures方法
假設(shè)用例文件結(jié)構(gòu)為:
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
@pytest.fixture
def other_username(username):
return 'other-' + username
test_something.py
# content of tests/test_something.py
import pytest
@pytest.mark.parametrize('username', ['directly-overridden-username'])
def test_username(username):
assert username == 'directly-overridden-username'
@pytest.mark.parametrize('username', ['directly-overridden-username-other'])
def test_username_other(other_username):
assert other_username == 'other-directly-overridden-username-other'
在上面的示例中胰蝠,username fixture方法的結(jié)果值被參數(shù)化值覆蓋歼培。 請注意,即使測試不直接使用(也未在函數(shù)原型中提及)茸塞,也可以通過這種方式覆蓋fixture的值躲庄。
使用非參數(shù)化fixture方法覆蓋參數(shù)化fixtures方法, 反之亦然
假設(shè)用例結(jié)構(gòu)為:
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture(params=['one', 'two', 'three'])
def parametrized_username(request):
return request.param
@pytest.fixture
def non_parametrized_username(request):
return 'username'
test_something.py
# content of tests/test_something.py
import pytest
@pytest.fixture
def parametrized_username():
return 'overridden-username'
@pytest.fixture(params=['one', 'two', 'three'])
def non_parametrized_username(request):
return request.param
def test_username(parametrized_username):
assert parametrized_username == 'overridden-username'
def test_parametrized_username(non_parametrized_username):
assert non_parametrized_username in ['one', 'two', 'three']
test_something_else.py
# content of tests/test_something_else.py
def test_username(parametrized_username):
assert parametrized_username in ['one', 'two', 'three']
def test_username(non_parametrized_username):
assert non_parametrized_username == 'username'
在上面的示例中,使用非參數(shù)化fixture方法覆蓋參數(shù)化fixture方法钾虐,以及使用參數(shù)化fixture覆蓋非參數(shù)化fixture以用于特定測試模塊噪窘。 這同樣適用于文件夾級別的fixtures方法。