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
,因为这样代码更加简洁高效,且阅读性更强更容易维护。