pytest.fixture
- setup和teardown函數(shù)能夠在測(cè)試用例之前或者之后添加一些操作靠抑,但這種是整個(gè)腳本全局生效的违施。
- 如果我們想實(shí)現(xiàn)以下場(chǎng)景:用例1需要登錄,用例2不需要登錄,用例3需要登錄,這就無(wú)法直接用setup和teardown來(lái)同一個(gè)類中實(shí)現(xiàn)泡一,卻可以通過pytest.fixture實(shí)現(xiàn)。
fixture基本使用
fixture是pytest特有的功能觅廓,它以裝飾器形式定義在函數(shù)上面鼻忠, 在編寫測(cè)試函數(shù)的時(shí)候,可以將被fixture裝飾的函數(shù)的名字做為測(cè)試函數(shù)的參數(shù)杈绸,運(yùn)行測(cè)試腳本時(shí)帖蔓,執(zhí)行測(cè)試函數(shù)時(shí)就會(huì)自動(dòng)傳入被fixture裝飾的函數(shù)的返回值矮瘟。
import pytest
import requests
# 0.@pytest.fixture裝飾函數(shù)
@pytest.fixture()
def get_web_url():
print('get_web_url')
return 'https://www.baidu.com'
# 1. 把上面函數(shù)名作為測(cè)試用例的參數(shù)
def test_web(get_web_url):
# 2. 測(cè)試用例調(diào)用前,需要先確定形參get_web_url塑娇,就是調(diào)用get_web_url
print('test_web')
print(get_web_url) # 測(cè)試用例內(nèi)部使用get_web_url澈侠,就是使用它返回值
r = requests.get(get_web_url)
assert r.status_code == 200, '測(cè)試成功'
運(yùn)行結(jié)果如下:
plugins: tavern-1.16.3, openfiles-0.3.2, arraydiff-0.3, allure-pytest-2.9.45, doctestplus-0.3.0, remotedata-0.3.1
collected 1 item
pytest_code5.py get_web_url
test_web
https://www.baidu.com
.
==================================================================== 1 passed in 0.19s ====================================================================
conftest.py文件
共享fixture函數(shù)
如果在測(cè)試中多個(gè)測(cè)試文件中用例用到同一個(gè)的fixture函數(shù),則可以將其移動(dòng)到conftest.py文件中埋酬,所需的fixture對(duì)象會(huì)自動(dòng)被pytest發(fā)現(xiàn)哨啃,而不需要再每次導(dǎo)入
conftest.py文件名固定
在conftest.py文件中實(shí)現(xiàn)共用的fixture函數(shù)
conftest.py內(nèi)容如下:
# pytest_fixture/conftest.py 文件名不能改變,否則無(wú)效
import pytest
# 默認(rèn)是function級(jí)別的
@pytest.fixture()
def login_fixture():
"""可以把函數(shù)作為參數(shù)傳遞"""
print("\n公用的登陸方法")
test_fixture1.py內(nèi)容如下:
# pytest_fixture/test_fixture1.py
import pytest
def test_get_carts():
"""購(gòu)物車不需要登陸"""
print("\n測(cè)試查詢購(gòu)物車,無(wú)需登錄")
class TestFixtures(object):
"""需要登陸的信息"""
def test_get_user_info(self, login_fixture):
print("獲取用戶信息")
def test_order_info(self, login_fixture):
print("查詢訂單信息")
def test_logout(login_fixture):
"""登出"""
print("退出登錄")
if __name__ == '__main__':
pytest.main(['-s', 'test_fixture1.py'])
運(yùn)行結(jié)果如下:
test_fixture1.py .
測(cè)試查詢購(gòu)物車奇瘦,無(wú)需登錄
公用的登陸方法
.獲取用戶信息
公用的登陸方法
.查詢訂單信息
公用的登陸方法
.退出登錄
[100%]
============================== 4 passed in 0.03s ===============================
pytest.mark.usefixtures
- 可以使用@pytest.mark.usefixtures('fixture函數(shù)名字符串')來(lái)裝飾測(cè)試類和測(cè)試方法
test_fixture2.py內(nèi)容如下:
# pytest_fixture/test_fixture2.py
import pytest
def test_get_carts():
"""購(gòu)物車不需要登陸"""
print("\n測(cè)試查詢購(gòu)物車棘催,無(wú)需登錄")
@pytest.mark.usefixtures('login_fixture')
class TestFixtures(object):
"""需要登陸的信息"""
def test_get_user_info(self):
print("獲取用戶信息")
def test_order_info(self):
print("查詢訂單信息")
@pytest.mark.usefixtures('login_fixture')
def test_logout():
"""登出"""
print("退出登錄")
if __name__ == '__main__':
pytest.main(['-s', 'test_fixture2.py'])
右鍵運(yùn)行如下:
test_fixture2.py .
測(cè)試查詢購(gòu)物車劲弦,無(wú)需登錄
公用的登陸方法
.獲取用戶信息
公用的登陸方法
.查詢訂單信息
公用的登陸方法
.退出登錄
[100%]
============================== 4 passed in 0.03s ===============================
pytest.fixture參數(shù)
pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)
- scope: 被標(biāo)記方法的作用域耳标, 可以傳入以下四個(gè)值;
-- "function": 默認(rèn)值,每個(gè)測(cè)試用例都要執(zhí)行一次 fixture 函數(shù)
-- "class": 作用于整個(gè)類邑跪, 表示每個(gè)類只運(yùn)行一次 fixture 函數(shù)
-- "module": 作用于整個(gè)模塊次坡, 每個(gè) module 的只執(zhí)行一次 fixture 函數(shù)
-- "session": 作用于整個(gè) session , 一次 session 只運(yùn)行一次 fixture - params: list 類型画畅,默認(rèn) None砸琅, 接收參數(shù)值,對(duì)于 param 里面的每個(gè)值轴踱,fixture 都會(huì)去遍歷執(zhí)行一次症脂。
- autouse: 是否自動(dòng)運(yùn)行,默認(rèn)為 false淫僻, 為 true 時(shí)此 session 中的所有測(cè)試函數(shù)都會(huì)調(diào)用 fixture
scope參數(shù)
- function:設(shè)置為function诱篷,表示每個(gè)測(cè)試方法都要執(zhí)行一次
# function:設(shè)置為function,表示每個(gè)測(cè)試方法都要執(zhí)行一次
import pytest
@pytest.fixture(scope='function')
# @pytest.fixture() # 和上面等價(jià)
def foo():
print('foo')
def test_1(foo):
print('普通測(cè)試用例111111')
def test_2():
print('普通測(cè)試用例22222')
class TestClass(object):
def test_one(self, foo):
print('類實(shí)例方法測(cè)試用例111111')
def test_two(self, foo):
print('類實(shí)例方法測(cè)試用例22222')
運(yùn)行如下:
test_9_scope_function.py foo
普通測(cè)試用例111111
.普通測(cè)試用例22222
.foo
類實(shí)例方法測(cè)試用例111111
.foo
類實(shí)例方法測(cè)試用例22222
.
==================================================================== 4 passed in 0.03s ====================================================================
- class:設(shè)置為 class 時(shí)代表這個(gè)類中只會(huì)執(zhí)行一次
import pytest
@pytest.fixture(scope='class')
def foo():
print('foo')
def test_1(foo):
print('普通測(cè)試用例111111')
def test_2(foo):
print('普通測(cè)試用例22222')
class TestClass(object):
def test_one(self, foo):
print('類實(shí)例方法測(cè)試用例111111')
def test_two(self, foo):
print('類實(shí)例方法測(cè)試用例22222')
運(yùn)行如下:
test_10_scope_class.py foo
普通測(cè)試用例111111
.foo
普通測(cè)試用例22222
.foo
類實(shí)例方法測(cè)試用例111111
.類實(shí)例方法測(cè)試用例22222
.
==================================================================== 4 passed in 0.03s ====================================================================
- module:設(shè)置為 module 時(shí)代表這個(gè)模塊中只會(huì)執(zhí)行一次
- session:整個(gè) session 都只會(huì)執(zhí)行一次
# module:只會(huì)在最開始的時(shí)候傳入?yún)?shù)執(zhí)行1次
# session:只會(huì)在session開始傳入?yún)?shù)的時(shí)候執(zhí)行1次
import pytest
@pytest.fixture(scope='module')
# @pytest.fixture(scope='session')
def foo():
print('foo')
def test_1(foo):
print('普通測(cè)試用例111111')
def test_2(foo):
print('普通測(cè)試用例22222')
class TestClass(object):
def test_one(self, foo):
print('類實(shí)例方法測(cè)試用例111111')
def test_two(self, foo):
print('類實(shí)例方法測(cè)試用例22222')
運(yùn)行如下:
test_11_scope_module.py foo
普通測(cè)試用例111111
.普通測(cè)試用例22222
.類實(shí)例方法測(cè)試用例111111
.類實(shí)例方法測(cè)試用例22222
.
==================================================================== 4 passed in 0.03s ====================================================================
params參數(shù)
- pytest.fixture(params=None) 的params參數(shù)接收l(shuí)ist類型的參數(shù)
- 對(duì)于param里面的每個(gè)值雳灵,fixture函數(shù)都會(huì)去遍歷執(zhí)行一次
- 相應(yīng)的每次都會(huì)驅(qū)動(dòng)使用fixture函數(shù)的測(cè)試函數(shù)執(zhí)行一次棕所。
import pytest
def check_password(password):
"""
檢查密碼是否合法
:param password: 長(zhǎng)度是 8 到 16
:return:
"""
pwd_len = len(password)
if pwd_len < 8:
return False
elif pwd_len > 16:
return False
else:
return True
@pytest.fixture(params=['1234567', '12345678', '123456789', '123456789012345', '1234567890123456', '12345678901234567'])
def password(request):
return request.param
def test_check_password(password):
print(password)
print(check_password(password))
運(yùn)行如下:
test_13_params.py 1234567
False
.12345678
True
.123456789
True
.123456789012345
True
.1234567890123456
True
.12345678901234567
False
.
==================================================================== 6 passed in 0.03s ====================================================================
import pytest
@pytest.fixture(params=['admin', 'zhangsan', 'lisi'])
def username(request):
return request.param
@pytest.fixture(params=['1234567', '12345678', '123456789', '123456789012345', '1234567890123456', '12345678901234567'])
def password(request):
return request.param
def test_check_regist(username, password):
print(username, '=====', password)
if __name__ == '__main__':
pytest.main(['-s', 'test_14_params2.py'])
運(yùn)行如下:
test_14_params2.py [100%]
============================== 18 passed in 0.06s ==============================
Process finished with exit code 0
.admin ===== 1234567
.admin ===== 12345678
.admin ===== 123456789
.admin ===== 123456789012345
.admin ===== 1234567890123456
.admin ===== 12345678901234567
.zhangsan ===== 1234567
.zhangsan ===== 12345678
.zhangsan ===== 123456789
.zhangsan ===== 123456789012345
.zhangsan ===== 1234567890123456
.zhangsan ===== 12345678901234567
.lisi ===== 1234567
.lisi ===== 12345678
.lisi ===== 123456789
.lisi ===== 123456789012345
.lisi ===== 1234567890123456
.lisi ===== 12345678901234567
autouse參數(shù)
pytest.fixture(autouse=False) 的autouse參數(shù)默認(rèn)為False, 不會(huì)自動(dòng)執(zhí)行;設(shè)置為True時(shí)悯辙,當(dāng)前運(yùn)行的所有測(cè)試函數(shù)在運(yùn)行前都會(huì)執(zhí)行fixture函數(shù)
import pytest
@pytest.fixture(autouse=True)
def before():
print('\nbefore each test')
class Test2:
def test_1(self):
print('test_5')
def test_2(self):
print('test_6')
運(yùn)行如下:
test_15_autouse.py
before each test
test_5
.
before each test
test_6
.
==================================================================== 2 passed in 0.03s ====================================================================
pytest.mark標(biāo)記
pytest.mark下提供了標(biāo)記裝飾器琳省,除了之前我們使用的pytest.mark.usefixtures()裝飾器以外,還有一些常用的標(biāo)記裝飾器
裝飾器 | 作用 |
---|---|
pytest.mark.xfail() | 將測(cè)試函數(shù)標(biāo)記為預(yù)期失敗躲撰。 |
pytest.mark.skip() | 無(wú)條件地跳過測(cè)試函數(shù) |
pytest.mark.skipif() | 有條件地跳過測(cè)試函數(shù) |
pytest.mark.parametrize() | 參數(shù)化Fixture方法和測(cè)試函數(shù)针贬。 |
pytest.mark.usefixtures() | 使用類、模塊或項(xiàng)目中的Fixture方法拢蛋。 |
標(biāo)志預(yù)期失效
- 要測(cè)試的功能或者函數(shù)還沒有實(shí)現(xiàn)桦他,這個(gè)時(shí)候執(zhí)行測(cè)試一定是失敗的。我們通過 xfail 來(lái)標(biāo)記某個(gè)測(cè)試方法一定會(huì)失敗
- xfail(condition=True, reason=None, raises=None, run=True, strict=False)
-- condition:標(biāo)記預(yù)期失敗的條件瓤狐,如果條件為 False瞬铸,那么這個(gè)標(biāo)記無(wú)意義
-- reason:標(biāo)記預(yù)期失敗的原因說明
import pytest
class Test_ABC:
def setup_class(self):
print("\nsetup")
def teardown_class(self):
print("\nteardown")
def test_a(self):
print("\ntest_a")
@pytest.mark.xfail(condition=False, reason="預(yù)期失敗")
def test_b(self):
print("\ntest_b")
assert 0
@pytest.mark.xfail(condition=True, reason="預(yù)期失敗")
def test_c(self):
print("\ntest_c")
assert 0
if __name__ == '__main__':
pytest.main(['-s', 'test_22.py'])
運(yùn)行如下:
test_22.py
setup
.
test_a
F
test_b
test_22.py:12 (Test_ABC.test_b)
self = <test_22.Test_ABC object at 0x7fc2295bfc18>
@pytest.mark.xfail(condition=False, reason="預(yù)期失敗")
def test_b(self):
print("\ntest_b")
> assert 0
E assert 0
test_22.py:16: AssertionError
x
test_c
self = <test_22.Test_ABC object at 0x7fc2295bfef0>
@pytest.mark.xfail(condition=True, reason="預(yù)期失敗")
def test_c(self):
print("\ntest_c")
> assert 0
E assert 0
test_22.py:21: AssertionError
teardown
Assertion failed
[100%]
=================================== FAILURES ===================================
_______________________________ Test_ABC.test_b ________________________________
self = <test_22.Test_ABC object at 0x7fc2295bfc18>
@pytest.mark.xfail(condition=False, reason="預(yù)期失敗")
def test_b(self):
print("\ntest_b")
> assert 0
E assert 0
test_22.py:16: AssertionError
----------------------------- Captured stdout call -----------------------------
test_b
=========================== short test summary info ============================
FAILED test_22.py::Test_ABC::test_b - assert 0
==================== 1 failed, 1 passed, 1 xfailed in 0.08s ====================
跳過測(cè)試函數(shù)
無(wú)條件跳過
使用場(chǎng)景: 根據(jù)特定條件批幌、不執(zhí)行標(biāo)識(shí)的測(cè)試函數(shù)
- skip(reason=None)
---reason: 標(biāo)注原因
import pytest
import pytest
class Test_ABC:
def setup_class(self):
print("\nsetup")
def teardown_class(self):
print("\nteardown")
def test_a(self):
print("test_a")
# 開啟跳過標(biāo)記
@pytest.mark.skip(reason="無(wú)條件跳過不執(zhí)行,就是任性頑皮")
def test_b(self):
print("test_b")
if __name__ == '__main__':
pytest.main(['-s', 'test_23.py'])
運(yùn)行效果如下:
test_23.py
setup
.test_a
s
Skipped: 無(wú)條件跳過不執(zhí)行嗓节,就是任性頑皮
teardown
[100%]
========================= 1 passed, 1 skipped in 0.03s =========================
有條件跳過
使用場(chǎng)景: 根據(jù)特定條件荧缘、不執(zhí)行標(biāo)識(shí)的測(cè)試函數(shù)
- skipif(condition, reason=None)
--- condition: 跳過的條件,必傳參數(shù)
--- reason: 標(biāo)注原因
import pytest
class Test_ABC:
def setup_class(self):
print("\nsetup")
def teardown_class(self):
print("\nteardown")
def test_a(self):
print("test_a")
# 開啟跳過標(biāo)記
@pytest.mark.skipif(condition=1, reason="有條件跳過不執(zhí)行拦宣,依舊任性頑皮")
# @pytest.mark.skipif(condition=0, reason="條件跳不成立截粗,無(wú)法跳過")
def test_b(self):
print("test_b")
if __name__ == '__main__':
pytest.main(['-s', 'test_24.py'])
運(yùn)行效果如下:
test_24.py
setup
.test_a
s
Skipped: 有條件跳過不執(zhí)行,依舊任性頑皮
teardown
[100%]
========================= 1 passed, 1 skipped in 0.03s =========================
參數(shù)化
使用場(chǎng)景:需要測(cè)試一組不同的數(shù)據(jù)鸵隧,而測(cè)試過程是一樣的绸罗,這種情況下我們可以寫一個(gè)測(cè)試方法,并且測(cè)試方法通過參數(shù)接受數(shù)據(jù)豆瘫。通過遍歷數(shù)據(jù)并且調(diào)用測(cè)試方法來(lái)完成測(cè)試珊蟀。
作用: 參數(shù)化fixture方法和測(cè)試函數(shù), 方便測(cè)試函數(shù)對(duì)測(cè)試屬性的獲取。
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
--- argnames:參數(shù)名外驱, 以逗號(hào)分隔的字符串育灸,表示一個(gè)或多個(gè)參數(shù)名稱,或參數(shù)字符串的列表/元組. 參數(shù)名為幾個(gè)昵宇,就會(huì)運(yùn)行幾次磅崭。
--- argvalues:
----參數(shù)對(duì)應(yīng)值,類型必須為 list
----當(dāng)參數(shù)為一個(gè)時(shí)瓦哎,參數(shù)格式:[value1,value2,...]
----當(dāng)參數(shù)個(gè)數(shù)大于一個(gè)時(shí)砸喻,格式為: [(param_value1,param_value2),...]
import pytest
class Test_ABC:
def setup_class(self):
print("setup")
def teardown_class(self):
print("teardown")
def test_a(self):
print("test_a")
@pytest.mark.parametrize("a", [3, 6])
def test_b(self, a):
print(f"test_b data:a={a}")
@pytest.mark.parametrize(["a","b"],[(1,2),(3,4)])
def test_c(self, a, b):
print(f"test_c a: {a}; b: ")
if __name__ == '__main__':
pytest.main(['-s', 'test_25.py'])
運(yùn)行效果如下
test_25.py [100%]
============================== 5 passed in 0.04s ===============================
Process finished with exit code 0
setup
.test_a
.test_b data:a=3
.test_b data:a=6
.test_c a: 1; b: 2
.test_c a: 3; b: 4
teardown