pytest解读fixtures之Teardown处理yield和addfinalizer方案
本文深入解析pytest中fixtures的teardown机制,详细对比yield和addfinalizer两种实现方式。通过具体代码示例展示yield fixtures的执行顺序及addfinalizer注册多个终结器的用法,并探讨两者在异常处理上的差异,帮助开发者编写更健壮的测试清理代码。
前言
在运行测试函数时,确保测试结束后能自动清理环境产生的影响至关重要。这样可以避免对其他测试函数造成干扰,防止测试数据随时间推移而不断累积。
熟悉unittest框架的开发者应该了解teardown函数的作用,其功能与清理环境类似。在此,我们将这类负责“善后”工作的代码统称为teardown代码。
pytest框架中的fixture机制同样提供了强大的支持,允许我们在其中定义teardown代码。
实现这一功能主要有两种方法,分别是yield和addfinalizer。
一、yield fixtures(推荐)
1, yield 和 return
在包含yield的fixtures函数中,关键字yield 可以替代 return,用于将fixture中的对象传递给调用该fixture的函数或测试函数。
其用法与其他普通fixture函数相似,主要区别在于:
yield替换掉了return- teardown代码放置在
yield之后
2, yield的执行顺序
pytest执行fixture函数时,会依据fixture之间的线性依赖关系按顺序调用。然而,当测试函数执行完毕后,pytest会按照之前的调用顺序反向执行yield之后的代码。
结合以下示例进行观察,此处未引用官方示例,而是手写一个更直观的案例:
import pytest
@pytest.fixture
def fixture_one():
print("\n执行fixture_one")
return 1
@pytest.fixture
def fixture_two(fixture_one):
print("\n执行fixture_two")
yield 2
print("\n执行fixture_two的teardown代码")
@pytest.fixture
def fixture_adding(fixture_one, fixture_two):
print("\n执行fixture_adding")
result = fixture_one + fixture_two
yield result
print("\n执行fixture_adding的teardown代码")
def test_demo(fixture_two, fixture_adding):
print("\n执行测试函数test_demo")
assert fixture_adding == 3
代码中,fixture函数调用了其他fixture,测试函数也调用了多个fixture。通过前几章的学习,相信读者已经能够梳理出前后的调用顺序:
- test_demo 测试函数首先调用fixture函数 fixture_two,随后调用 fixture_adding。
- 在fixture函数 fixture_two中,进一步调用了另一个fixture函数 fixture_one。
- 在fixture函数 fixture_adding中,调用了 fixture_one和 fixture_two。
因此,fixture函数的初始化顺序为:fixture_one、fixture_two、fixture_adding。据此可推断,测试结束后的teardown代码执行顺序为:fixture_adding、fixture_two。
运行代码以验证结果是否符合上述梳理:
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: D:\练习\demo_fixture
plugins: allure-pytest-2.8.32, celery-4.3.0, Faker-4.14.2, base-url-1.4.2, html-2.1.1, metadata-1.10.0collected 1 item
test_module.py
执行fixture_one
执行fixture_two
执行fixture_adding
.
执行测试函数test_demo
执行fixture_adding的teardown代码
执行fixture_two的teardown代码
[100%]
============================== 1 passed in 0.09s ==============================
运行结果与之前的分析完全一致。
需要注意的是,即使teardown代码按正确顺序执行,也不能保证所有代码都能正常执行。例如,若teardown中的某段代码发生异常,可能导致后续的清理动作无法执行。这引出了另一个关键点:健壮的fixture结构应具备何种特性。官方文档对此有单独说明,此处亦不作展开。
二、addfinalizer
1.request.addfinalizer把函数变成终结器
在pytest中进行teardown处理,除了使用带有yield的fixture函数外,还可以直接添加终结器。直接参考示例代码:
import pytest
@pytest.fixture()
def demo_fixture(request):
print("\n这个fixture在每个case前执行一次")
def demo_finalizer():
print("\n在每个case完成后执行的teardown")
#注册demo_finalizer为终结函数
request.addfinalizer(demo_finalizer)
def test_01(demo_fixture):
print("\n===执行了case: test_01===")
def test_02(demo_fixture):
print("\n===执行了case: test_02===")
查看运行结果:
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: D:\练习\demo_fixture
plugins: allure-pytest-2.8.32, celery-4.3.0, Faker-4.14.2, base-url-1.4.2, html-2.1.1, metadata-1.10.0collected 2 items
test_module.py
这个fixture在每个case前执行一次
.
===执行了case: test_01===
在每个case完成后执行的teardown
这个fixture在每个case前执行一次
.
===执行了case: test_02===
在每个case完成后执行的teardown
[100%]
============================== 2 passed in 0.10s ==============================
Process finished with exit code 0
从运行结果可以看出,其效果与yield一致。这是一种固定写法,关于request的文档中另有讲解,届时再行分享。
2.request.addfinalizer注册多个终结器函数
上述代码仅展示了一个终结函数,若要注册多个终结器该如何操作?
import pytest
@pytest.fixture()
def demo_fixture(request):
print("\n这个fixture在每个case前执行一次")
def demo_finalizer():
print("\n在每个case完成后执行的teardown")
def demo_finalizer2():
print("\n在每个case完成后执行的teardown2")
#注册demo_finalizer为终结函数
request.addfinalizer(demo_finalizer)
request.addfinalizer(demo_finalizer2)
def test_01(demo_fixture):
print("\n===执行了case: test_01===")
def test_02(demo_fixture):
print("\n===执行了case: test_02===")
if __name__ == '__main__':
pytest.main(['-s', 'test_module.py'])
运行结果:
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: D:\练习\demo_fixture
plugins: allure-pytest-2.8.32, celery-4.3.0, Faker-4.14.2, base-url-1.4.2, html-2.1.1, metadata-1.10.0collected 2 items
test_module.py
这个fixture在每个case前执行一次
.
===执行了case: test_01===
在每个case完成后执行的teardown2
在每个case完成后执行的teardown
这个fixture在每个case前执行一次
.
===执行了case: test_02===
在每个case完成后执行的teardown2
在每个case完成后执行的teardown
[100%]
============================== 2 passed in 0.09s ==============================
Process finished with exit code 0
这里需要特别注意,在注册多个终结器的情况下,其执行顺序与注册时的顺序相反。
3.yield和addfinalizer的区别
目前从官方文档中了解到:
We have to be careful though, because pytest will run that finalizer once it’s been added, even if that fixture raises an exception after adding the finalizer.
一旦添加了终结器,pytest便会执行它。
然而,当我尝试在setup代码中进行抛错时,终结器的代码并未执行。
尝试搜索外网暂时未得到有效帮助,只能在GitHub上向pytest提交issue。此处暂留一个待解决的坑,更多关于Teardown处理yield和addfinalizer的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









