【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后置代码是不执行的,但终结函数是不受影响的.