通過上一篇文章,我們已經知道了pytest
中,可以使用Fixture
來完成運行測試用例之前的一些操作如連接數據庫,以及測試執行之后自動去做一些善后工作如清空臟數據、關閉數據庫連接等。
我們已經學會了fixture
函數的簡單用法,但其實fixture
還提供了兩種非常優雅高效的寫法,來完成測試執行前的處理操作與執行后的處理操作,即使用yield
或addfinalizer
來實現。
yield
在fixture中的關鍵字yield主要有兩個作用:
yield
代替return
進行參數的傳遞- 起到代碼的分割作用,yield之前的代碼為
setup
的作用,yield之后的代碼為teardown
的作用
yield 與 return
在 pytest 的fixture
函數中可以使用yield
代替return
進行返回,示例如下:
import pytest
@pytest.fixture(autouse=True)
def fixture_one():
print("執行fixture_one")
yield 1
def test_e(fixture_one):
print("執行test_e")
print(fixture_one)
assert fixture_one == 1
if __name__ == '__main__':
pytest.main(["-s"])
運行結果如下:
test_case_4.py::test_e
執行fixture_one
PASSED [100%]執行test_e
1
============================== 1 passed in 0.12s ==============================
從運行結果我們能看到fixture_one
會返回1
並傳遞給test_e
,與return
的作用完全一致。但如果僅僅只是這樣使用的話,毫無意義,因為使用return
足夠了。所以,在實際的使用過程中我們一般會在yield
后面加上teardown
的代碼。
yield 與 teardown
yield不進行參數傳遞
對於不需要在前置操作中返回數據的 fixture 函數,加入yield
,那么yield之前的代碼為用例執行之前的操作(即setup),yield之后的代碼為用例執行之后的操作(即teardown)。示例如下:
import pytest
@pytest.fixture()
def fixture_demo():
# setup
print("\n連接數據庫")
yield
# teardown
print("清空臟數據")
def test_case(fixture_demo):
print("執行test_case")
assert True
if __name__ == '__main__':
pytest.main(["-s"])
運行結果如下:
從結果中我們可以看出來,先執行了setup部分,再執行測試用例,最后執行teardown部分。
yield進行參數傳遞
yield
可以將參數傳遞給測試用例。
假設有這樣一個場景,需要用到接口1
的返回參數作為接口2
的請求參數,即接口2
依賴接口1
,我們需要寫一條測試用例對接口2
進行測試,這個時候可以將接口1
的請求寫在前置中,如果是unittest
框架則代碼如下:
import unittest
import requests
class TestDemo(unittest.TestCase):
def setup(self):
print("請求接口1")
self.res_1 = requests.get(url=url_1, params=params_1)
def test_api_2(self):
print("驗證接口2")
# 將接口1的返回值self.res_1作為請求參數,請求接口2
res = requests.post(url=url_2, data=self.res_1)
# 斷言
self.assertEqual(res, "接口2預期的返回結果")
def teardown(self):
print("清空臟數據")
在pytest
框架中使用fixture
+yield
則可編寫如下:
@pytest.fixture()
def get_api_1_result():
# setup
res_1 = requests.get(url=url_1, params=params_1)
yield res_1
# teardown
print("清空臟數據")
def test_api_2(get_api_1_result):
print("驗證接口2")
# 將接口1的返回值res_1作為請求參數,請求接口2
res = requests.post(url=url_2, data=get_api_1_result)
# 斷言
assert res == "接口2預期的返回結果"
其中,fixture 會先通過yield
返回res_1
,並傳入測試用例test_api_2
中,test_api_2
運行完成后再去執行yield
后面的代碼,即執行print("清空臟數據")
。
通過以上對比unittest
中setup
、teardown
以及參數的傳遞,我們就能很直觀的看出pytest
中yield
的使用方式,此處代碼僅為示例。
yield 的執行順序
有時候我們會遇到一個fixture函數調用另一個或多個fixture函數,且這些函數中可能含有yield,我們先看示例,代碼如下:
import pytest
@pytest.fixture
def fixture_1():
print("\n執行fixture_1")
yield 1
print("\n執行fixture_1的teardown代碼")
@pytest.fixture
def fixture_2(fixture_1):
print("\n執行fixture_2")
yield 2
print("\n執行fixture_2的teardown代碼")
@pytest.fixture
def fixture_add(fixture_1, fixture_2):
print("\n執行fixture_add")
result = fixture_1 + fixture_2
yield result
print("\n執行fixture_add的teardown代碼")
def test_demo(fixture_add):
print("\n執行測試函數test_demo")
assert fixture_add == 3
if __name__ == '__main__':
pytest.main(["-s"])
運行結果如下:
rootdir: E:\blog\python接口自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 1 item
test_case_4.py::test_demo
執行fixture_1
執行fixture_2
執行fixture_add
PASSED [100%]
執行測試函數test_demo
執行fixture_add的teardown代碼
執行fixture_2的teardown代碼
執行fixture_1的teardown代碼
============================== 1 passed in 0.12s ==============================
從結果可以看出:
test_demo 測試函數執行之前:先執行了 fixture_1,再執行fixture_2,最后執行fixture_add,注意此時都是執行yield之前的的代碼;
test_demo 測試函數執行之后:先執行了 fixture_add,再執行fixture_2,最后執行fixture_1,注意此時都是執行yield之后的的代碼。
因此,當一個fixture函數調用另一個或多個fixture函數,且fixture函數中含有yield
時,被測試函數調用時有如下執行順序:
-
測試函數執行之前,pytest會根據fixture函數之間的線性關系順序調用,即依次執行
yield
之前的代碼。 -
而測試函數執行結束后,pytest會根據之前的順序反方向執行
fixture
函數中yield
之后的代碼。
finalizer
finalizer
即終結器 (終結函數),與unittest中的teardown
作用一樣,測試用例執行完成后再執行終結器代碼。
在pytest中,fixture除了使用 yield 進行 teardown 之外,還可以使用request.addfinalizer()
定義finalizer
來進行后置操作。
使用addfinalizer
,需要在定義 fixture 函數時傳入request
,並以內嵌函數的形式進行定義。終結函數可以定義一個或多個。
定義單個終結函數
示例如下:
import pytest
@pytest.fixture
def fixture_demo(request):
print("\nsetup:每個case開始前執行一次")
# 定義終結函數
def finalizer_demo():
print("\nteardown:每個case完成后執行一次")
# 將finalizer_demo注冊為終結函數
request.addfinalizer(finalizer_demo)
def test_2(fixture_demo):
print("\n執行test_2")
def test_1(fixture_demo):
print("\n執行test_1")
if __name__ == '__main__':
pytest.main()
運行結果如下:
rootdir: E:\blog\python接口自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 2 items
test_module_02\test_case_3.py::test_2
setup:每個case開始前執行一次
PASSED [ 50%]
執行test_2
teardown:每個case完成后執行一次
test_module_02\test_case_3.py::test_1
setup:每個case開始前執行一次
PASSED [100%]
執行test_1
teardown:每個case完成后執行一次
============================== 2 passed in 0.03s ==============================
從結果可以看出來,在測試用例執行完后會執行addfinalizer
函數,效果與執行yield
后的代碼一致。
定義多個終結函數
示例如下:
import pytest
@pytest.fixture
def fixture_demo(request):
print("\nsetup:每個case開始前執行一次")
# 定義終結函數
def finalizer_demo_1():
print("\nteardown1:每個case完成后執行一次")
def finalizer_demo_2():
print("\nteardown2:每個case完成后執行一次")
# 注冊為終結函數
request.addfinalizer(finalizer_demo_1)
request.addfinalizer(finalizer_demo_2)
def test_2(fixture_demo):
print("\n執行test_2")
def test_1(fixture_demo):
print("\n執行test_1")
if __name__ == '__main__':
pytest.main()
運行結果如下:
rootdir: E:\blog\python接口自動化\apiAutoTest, configfile: pytest.ini
plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
collecting ... collected 2 items
test_module_02\test_case_3.py::test_2
setup:每個case開始前執行一次
PASSED [ 50%]
執行test_2
teardown2:每個case完成后執行一次
teardown1:每個case完成后執行一次
test_module_02\test_case_3.py::test_1
setup:每個case開始前執行一次
PASSED [100%]
執行test_1
teardown2:每個case完成后執行一次
teardown1:每個case完成后執行一次
============================== 2 passed in 0.02s ==============================
從結果可以看出,上面示例中測試函數執行完成后,先執行了finalizer_demo_2
,后執行finalizer_demo_1
。
所以, 當有多個終結函數被執行時,執行順序與注冊順序是相反的。
總結
實際項目中,可以視情況進行選擇,但一般情況下,推薦使用yield
,因為這樣代碼更加簡潔高效,且閱讀性更強更容易維護。