测试夹具(test fixture)

测试夹具:适用于所有测试代码的初始化和清理的(代码)方法

在 Pytest 中也进行了阐述:

Software test fixtures initialize test functions. They provide a fixed baseline so that tests execute reliably and produce consistent, repeatable, results. Initialization may setup services, state, or other operating environments. These are accessed by test functions through arguments; for each fixture used by a test function there is typically a parameter (named after the fixture) in the test function’s definition.

—— AOP 的一种实现。延伸阅读 - IoC/DI
image.png

pytest 的测试夹具

Pytest only caches one instance of a fixture at a time, which means that when using a parametrized fixture, pytest may invoke a fixture more than once in the given scope. https://docs.pytest.org/en/stable/how-to/index.html https://docs.pytest.org/en/stable/reference/fixtures.html

作用域

Pytest 的测试夹具使用是装饰器 @pytest.fixture(scope=) ,与作用域对应。即可以是

  • session 从 Pytest 测试执行开始,直到测试结束后销毁
  • package 当前包下的测试结束后销毁
  • module 当前模块下的测试结束后销毁
  • class 当前类下的测试结束后销毁
  • function 当前函数/方法结束后销毁

例如:

  1. # content of conftest.py
  2. import pytest
  3. import smtplib
  4. @pytest.fixture(scope="module")
  5. def smtp_connection():
  6. return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)

使用方式

用途诸如参数传递、共享数据和参数化的支持等,比如测试用例执行总会需要”登录”状态,数据库连接的建立等等。因为较为庞大与灵活,这里只提及几个常见且重要的能力,具体的更多的技巧方式见 官方 fixture 的使用。 需要注意的是,fixture 实例化是有顺序/优先级的,且 @pytest.mark.usefixtures@pytest.fixture 叠用有不可预测性


1. 作为测试函数的参数传入 (接上个示例)

  1. def test_ehlo(smtp_connection): #夹具smtp_connection作为参数让函数 test_ehlo 接收。其实这就是 DI 实现
  2. response, msg = smtp_connection.ehlo() #调用夹具实例smtp_connection
  3. assert response == 250

2. 放入 conftest.py 共享夹具,这样也省去了导入工作

it automatically gets discovered by pytest. The discovery of fixture functions starts at test classes, then test modules, then conftest.py files and finally builtin and third party plugins

image.png

引用该共享夹具见下文第8项 夹具对类和模块的支持 (使用@pytest.mark.usefixtures

3. 共享测试数据(同时,pytest 会缓存这些数据)

4. 作为初始化和销毁。即 AOP 中的Before、After

image.png

5. 将测试函数的数据传递给夹具

_@pytest.mark.fixt_data()_ 个人建议:对于此类诉求,不借助夹具参数反转能力,而是使用函数的参数。 那么此类能力的场景适用于什么时候?——数据驱动的模型

image.png
image.png

6. 模块化实现:从夹具函数使用夹具

image.png
image.png

7. 通过夹具自动将测试函数分组

  • 可以通过 pytest.mark 的能力
  • 可以通过作用域

8. 夹具对类和模块的支持 (使用@pytest.mark.usefixtures

image.png
image.png

对 xunit 形式的夹具支持

官方参考文档传送门

While these setup/teardown methods are simple and familiar to those coming from a unittest or nose background, you may also consider using pytest’s more powerful fixture mechanism which leverages the concept of dependency injection, allowing for a more modular and more scalable approach for managing test state, especially for larger projects and for functional testing. You can mix both fixture mechanisms in the same file but test methods of unittest.TestCase subclasses cannot receive fixture arguments.

image.png

Pytest 测试文件发现

测试文件发现将默认递归(recurse)查询指定规则的py文件。

  1. 默认的测试文件发现规则
  • 包名 test_*.py*_test.py 以及导入的符合规则的包
  • 类名称为 Test 前缀。且类不能有 __init__() 构造方法
  • 函数名为 test_ 前缀。

——对于这类通用默认模式,最好不要做个性化,就像 Python 的 _self_
image.png

  1. py包中 __init__.py 对测试文件发现的作用与影响

image.png

测试跳过与指定失败

https://docs.pytest.org/en/stable/skipping.html#skip-and-xfail-dealing-with-tests-that-cannot-succeed

这个用途往往用在 TODO 或者 可预期失败的用例中

命令行

pytest 命令执默认从用户当前所在目录开始执行与测试发现规则匹配的文件,指定了执行地址和文件等规则除外。具体支持的命令见帮助文档

pytest -h 查阅可支持命令,支持的命令参数非常多。
image.png

常用的一些参数

  1. -v 输出执行的详细信息(可以用于 debug)
  2. --pdb 调试信息管理器。更多调试的方式与信息见 PDB
  3. -s 异常捕获的快捷模式。比如显示 print 方法,异常抛出时的简要描述等
  4. -m 执行指定的测试文件
  5. --html=path 生成 html 格式测试报告到指定的path路径
  6. --full-trace 详细的链路追踪
  7. --runxfail 忽略 xfail 标记,执行测试并生成报告
  8. -x 测试失败即退出,不再继续执行
  9. --lf 重执行最近一次失败的用例,没有失败则执行全部
  10. --ff 执行所有测试用例,失败的优先
  11. --maxfail=num 限定次数失败后停止测试
  12. --ignore=path 不执行指定的文件
  13. --alluredir=DIR 生成 allure 报告到指定路径

命令执行示例:

  1. $ python3 -v -m pytest website_run_test.py --html=../../reports/WebsiteTestReport.html --self-contained-html

Pycharm 配置示例:
image.png

一些其他的特性调用补充

如指定执行的类型、范围,或是函数、类等

image.png

markers 的使用

  1. def pytest_configure(config):
  2. """
  3. 通过 @pytest.mark 调用。用于测试用例管理
  4. e.g @pyetest.mark.bvt 即定义某用例级别为构建认证测试(金丝雀测试)
  5. """
  6. marker_list = ["bvt", "smoke"]
  7. for markers in marker_list:
  8. config.addinivalue_line("markers", markers)
  1. @pytest.mark.bvt
  2. @allure.severity(allure.severity_level.BLOCKER)
  3. def test_login(self, browser):
  4. """正常登录"""
  5. login(browser)

image.png