前言
在之前的介绍中,我们已经介绍了fixture的简单用法,但其实fixture还提供了两种非常优雅高效的写法,来完成测试执行前的处理操作与执行后的处理操作,即使用yield
或addfinalizer
来实现。本文我们将介绍使用yield来实现操作。
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"])
--------------------
运行结果如下:
============================= test session starts =============================
collecting ... collected 1 item
test_demo01.py::test_case
连接数据库
PASSED [100%]执行test_case
清空脏数据
============================== 1 passed in 0.02s ==============================
从结果中我们可以看出来,先执行了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"])
-----------
执行结果如下:
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之后的代码。
总结
总的来说,yield
关键字为 Pytest fixture 提供了一种优雅的方式来处理资源管理、测试环境设置和清理工作,使得测试代码更加健壮和可靠。希望本文能够帮到大家!