【pytest-fixture】六、fixture通过yield或addfinalizer完成拆卸/清理
我基本上是跟着官网去学习fixture,自己先看并学了一遍,为了方便后续查看和记忆,所以将学习笔记记录下来,如果有不对的地方,欢迎大家评论区指出。
目录
当我们运行我们的测试时,我们会希望确保它们自己清理干净,这样它们就不会与任何其他测试混淆(同时我们也不会留下大量的测试数据来使系统膨胀)。pytest 中的 Fixtures 提供了一个非常有用的拆卸系统,它允许我们定义每个 Fixture 自行清理所需的特定步骤。分别是通过yield或addfinalizer完成清理,下面分别对这两种方式进行展开说明。
1.yield方式完成清理(推荐)
1.1介绍
使用yield的fixture和普通的fixture基本差不多,但还是有以下两点:
- return改换成yield。
- 需要做清理的代码写在yield的下面。
使用yield后,fixture的执行顺序是这样的,如有两个fixture函数,fixture1和fixture2,先由pytest计算出fixture的线性顺序,它将运行每个fxiture直到它返回,然后移动到列表中的下一个夹具做同样的事情。等待测试完成后,pytest将返回到fxiture列表,但顺序相反,如果开始先执行了fixture1再执行了fixture2,那么后面就是先执行fixture2在执行fixture1.
1.2代码示例
文件名:test_demo.py
import pytest
@pytest.fixture
def get_token():
print("请求获取token")
yield
# 这里写你的清理代码
print("注销token")
def test_demo():
print("测试用例")
1.3运行结果
1.4结果分析
通过结果展示,我们可以清楚的看到我们的清理代码在测试用例运行过后运行了。这种方式比我们用setup和teardown要好很多,可以抽取公共代码,减少代码冗余。
1.5举例说明多个yield的fixture执行顺序
- 示例代码
文件名: test_demo.py
import pytest
@pytest.fixture
def fn1():
print("我是fn1,我在yield前面")
yield 1
print("我是fn1,我在yield后面")
@pytest.fixture
def fn2(fn1):
print("我是fn2,我在yield前面")
yield 2
print("我是fn2,我在yield后面")
def test_demo(fn2):
print("我是测试用例")
- 运行结果
- 结果分析
通过上述例子,我们可以很明显知道yield的fixture的执行顺序
pytest先计算出线性顺序,先执行了fn1,然后再执行了fn2,再执行test,清理时就先执行的fn2,然后再执行fn1,与开始的顺序相反。
1.6对于yield出错后的执行方式说明
还是通过代码和运行的方式进行说明
- 实例代码
文件名: test_demo.py
import pytest
@pytest.fixture
def fn1():
print("我是fn1,我在yield前面")
yield 1
print("我是fn1,我在yield后面")
@pytest.fixture
def fn2(fn1):
print("我是fn2,我在yield前面")
1/0
yield 2
print("我是fn2,我在yield后面")
def test_demo(fn2):
print("我是测试用例")
- 运行结果
- 结果分析
通过1.5可以知道它的执行顺序是,fn1前置代码,fn2前置代码,测试代码,fn2后置代码,fn1后置代码.
我们现在在fn2前置代码中手动引发错误,此时通过上述结果可以发现,fn2前置出现错误后,fn2的后置代码和测试用例都不会执行,但是前面fn1前置执行了,结束时会执行fn1后置代码.
总结:如果yield fixture引发了异常,pytest将不会尝试运行该yield fixture的yield语句之后它的清理代码和测试用例,但是,对于已经运行成功的带有yield的fixtrue,pytest仍将运行它们yield之后的代码.
2.使用addfinalizer方法完成清理
2.1介绍
虽然yield fixture被认为是更简洁和更直接的方式,但还有另外一种选择,那就是将"终结器"函数直接添加到测试的请求上下文对象中.它有着和yield fixture相似的结果.
如何使用此方法,我们必须在需要为其添加清理代码的fixture中请求上下分对象(request)(就像我们请求另一个fixture一样),然后将包含清理器代码的可调用对象传递给它的addfinalizer方法.
不过需要小心,因为一旦添加了终结器,pytest就会运行该终结器,即使该夹具在添加终结器后引发异常也是如此.因此,为了确保我们不会再不需要时运行终结器代码,我们只会在夹具完成我们需要清理的事情时添加终结器.
2.2实例代码
文件名: test_demo.py
import pytest
@pytest.fixture
def login(request):
print("登陆前置")
def clear():
print("登陆后置,清理代码")
request.addfinalizer(clear)
yield
print("我是login yield的代码")
@pytest.fixture
def setup(login, request):
print("前置代码")
def clear():
print("setup后置代码")
request.addfinalizer(clear)
yield
print("后置代码")
def test_demo(setup):
print("测试用例")
2.3运行结果
2.4结果分析
通过运行结果,我们可以看出,通过在上下分中添加清理代码,和我们yield fixfure最终结果是差不多的,但是如果fxiture中既有yield也有在上下分中添加终极器,那么yield会先执行,执行完后在执行终结器中的函数,同时还有就是上面可以看到setup的终结器和login的终结器执行顺序是和yield执行的顺序是一致的.
3.两种方式适用的场景
yield是更推荐使用的,我们可以看到使用终结函数代码并没有yield简洁且直观,虽然yield fixture当前报错后,不会执行清理代码,但后面我们会讲设置fixtrue尽量保持原子性,如果保持原子性的话,大家是互不影响的.且前置代码运行失败后,对象也就没创建成功,所以咱们可以不用担心这一点.当然有的场景我们也会用到终结函数,就是我们必须要进行的一些清理动作,即使报错也要运行的内容,这种情况我们就需要用到终结函数,下面我们也同样上一段代码来看看
- 代码示例
文件名: test_demo.py
import pytest
@pytest.fixture
def login(request):
print("登陆前置")
def clear():
print("登陆后置,清理代码")
request.addfinalizer(clear)
1/0
yield
print("我是login yield的代码")
def test_demo(login):
print("测试用例")
- 运行结果
- 结果分析
可以看到,出现错误后,yield后置代码是不执行的,但终结函数是不受影响的.