關(guān)于pytest fixtures,根據(jù)官方文檔介紹: fixture 用于提供一個固定的基線,使 Cases 可以在此基礎(chǔ)上可靠地、重復(fù)地執(zhí)行。對比 PyUnit 經(jīng)典的setup/teardown形式,它在以下方面有了明顯的改進(jìn):
- fixture擁有一個明確的名稱,通過聲明使其能夠在函數(shù)、類、模塊,甚至整個測試會話中被激活使用;
- fixture以一種模塊化的方式實(shí)現(xiàn),原因在于每一個fixture的名字都能觸發(fā)一個fixture函數(shù),而這個函數(shù)本身又能調(diào)用其它的fixture;
- fixture的管理從簡單的單元測試擴(kuò)展到復(fù)雜的功能測試,允許通過配置和組件選項(xiàng)參數(shù)化fixture和測試用例,或者跨功能、類、模塊,甚至整個測試會話復(fù)用fixture;
一句話概括:在整個測試執(zhí)行的上下文中,fixture扮演注入者(injector)的角色,而測試用例扮演消費(fèi)者(client)的角色,測試用例可以輕松的接收和處理需要預(yù)初始化操作的應(yīng)用對象,而不用過分關(guān)心其實(shí)現(xiàn)的具體細(xì)節(jié)。
fixture的實(shí)例化順序
fixture支持的作用域(Scope):function(default)、class、module、package、session。
其中,package作用域是在 pytest 3.7 的版本中,正式引入的,目前仍處于實(shí)驗(yàn)性階段。
多個fixture的實(shí)例化順序,遵循以下原則:
- 高級別作用域的(例如:session)優(yōu)先于 低級別的作用域的(例如:class或者function)實(shí)例化;
- 相同級別作用域的,其實(shí)例化順序遵循它們在測試用例中被聲明的順序(也就是形參的順序),或者fixture之間的相互調(diào)用關(guān)系;
- 指明
autouse=True的fixture,先于其同級別的其它fixture實(shí)例化。
fixture 實(shí)現(xiàn) teardown 功能
有以下幾種方法:
注意:在yield之前或者addfinalizer注冊之前代碼發(fā)生錯誤退出的,都不會再執(zhí)行后續(xù)的清理操作。
- 將fixture變?yōu)樯善鞣椒ǎㄍ扑])
即將fixture函數(shù)中的return關(guān)鍵字替換成yield,則yield之后的代碼,就是我們要的清理操作。
@pytest.fixture(scope='session', autouse=True)
def clear_token():
yield
from libs.redis_m import RedisManager
rdm = RedisManager()
rdm.expire_token(seconds=60)
- 使用
addfinalizer方法
fixture函數(shù)能夠接收一個request的參數(shù),表示測試請求的上下文(下面會詳細(xì)介紹),我們可以使用request.addfinalizer方法為fixture添加清理函數(shù)。
@pytest.fixture()
def smtp_connection_fin(request):
smtp_connection = smtplib.SMTP("smtp.163.com", 25, timeout=5)
def fin():
smtp_connection.close()
request.addfinalizer(fin)
return smtp_connection
- 使用
with寫法(不推薦)
對于支持with寫法的對象,我們也可以隱式的執(zhí)行它的清理操作:
@pytest.fixture()
def smtp_connection_yield():
with smtplib.SMTP("smtp.163.com", 25, timeout=5) as smtp_connection:
yield smtp_connection
fixture可以訪問測試請求的上下文
fixture函數(shù)可以接收一個request的參數(shù),表示測試用例、類、模塊,甚至測試會話的上下文環(huán)境;
例如可以擴(kuò)展下上面的smtp_connection_yield,讓其根據(jù)不同的測試模塊使用不同的服務(wù)器:
@pytest.fixture(scope='module')
def smtp_connection_request(request):
server, port = getattr(request.module, 'smtp_server', ("smtp.163.com", 25))
with smtplib.SMTP(server, port, timeout=5) as smtp_connection:
yield smtp_connection
print("斷開 %s:%d" % (server, port))
在測試模塊中指定smtp_server:
smtp_server = ("mail.python.org", 587)
def test_163(smtp_connection_request):
response, _ = smtp_connection_request.ehlo()
assert response == 250
fixture返回工廠函數(shù)
如果需要在一個測試用例(function)中,多次使用同一個fixture實(shí)例,相對于直接返回?cái)?shù)據(jù),更好的方法是返回一個產(chǎn)生數(shù)據(jù)的工廠函數(shù)。并且,對于工廠函數(shù)產(chǎn)生的數(shù)據(jù),也可以在fixture中對其管理:
@pytest.fixture
def make_customer_record():
# 記錄生產(chǎn)的數(shù)據(jù)
created_records = []
# 工廠
def _make_customer_record(name):
record = models.Customer(name=name, orders=[])
created_records.append(record)
return record
yield _make_customer_record
# 銷毀數(shù)據(jù)
for record in created_records:
record.destroy()
def test_customer_records(make_customer_record):
customer_1 = make_customer_record("Lisa")
customer_2 = make_customer_record("Mike")
customer_3 = make_customer_record("Meredith")
fixture的參數(shù)化
如果你需要在一系列的測試用例的執(zhí)行中,每輪執(zhí)行都使用同一個fixture,但是有不同的依賴場景,那么可以考慮對fixture進(jìn)行參數(shù)化;這種方式適用于對多場景的功能模塊進(jìn)行詳盡的測試。
@pytest.fixture(scope='module', params=['smtp.163.com', "mail.python.org"])
def smtp_connection_params(request):
server = request.param
with smtplib.SMTP(server, 587, timeout=5) as smtp_connection:
yield smtp_connection
def test_parames(smtp_connection_params):
response, _ = smtp_connection_params.ehlo()
assert response == 250
在不同的層級上覆寫fixture
注意:低級別的作用域可以調(diào)用高級別的作用域,但是高級別的作用域調(diào)用低級別的作用域會返回一個ScopeMismatch的異常。
在大型的測試中,可能需要在本地覆蓋項(xiàng)目級別的fixture,以增加可讀性和便于維護(hù):
@pytest.fixture(scope="module", autouse=True)
def init(frag_login):
pass
@pytest.fixture(scope='session')
def active_user_account(cmd_line_args, conf):
tail_num = cmd_line_args.get("tailnum", None)
if tail_num is None:
tail_num = "1"
for user in conf['unified']:
if str(user['uid'])[-1] == tail_num:
return user
msg = f"尾號[{tail_num}], 在配置文件中未找到"
logger.error(msg)
raise ValueError(msg)