wordpress下载收费,黑客入侵别人网站做seo,黄页88怎么发信息质量高,龙岩公司注册流程Pytest测试框架 一、前言二、安装2.1 命令行安装2.2 验证安装 三、pytest设计测试用例注意点3.1 命名规范3.2 断言清晰3.3 fixture3.4 参数化设置3.5 测试隔离3.6 异常处理3.7 跳过或者预期失败3.8 mocking3.9 标记测试 四、以案例初入pytest4.1 第一个pytest测试4.2 多个测试分… Pytest测试框架 一、前言二、安装2.1 命令行安装2.2 验证安装 三、pytest设计测试用例注意点3.1 命名规范3.2 断言清晰3.3 fixture3.4 参数化设置3.5 测试隔离3.6 异常处理3.7 跳过或者预期失败3.8 mocking3.9 标记测试 四、以案例初入pytest4.1 第一个pytest测试4.2 多个测试分组一个类里面4.3 将多个测试用例分组4.4 pytest运行中命令行运行可选参数 五、配置文件pytest.ini六、conftest文件6.1 conftest的关键点6.2 conftest案例 七、mark属性标记7.1 内置/自定义标记7.2 pytest.mark.parametrize参数化测试函数 八、Fixture装饰器8.1 基本概念8.2 Fixture方法解析8.3 Fixture的创建和使用8.4 调用Fixture的方式 九 、pytest跳过测试用例方法9.1 skip -- 跳过测试用例9.2 skipif -- 有条件跳过测试用例 十、失败重跑10.1 使用 --last-failed 选项10.2 使用 --failed-first 选项10.3 使用 pytest-rerunfailures 插件 十一、pytest执行顺序十二、hooks钩子函数12.1 钩子函数的四个阶段12.1.1 配置阶段 (Configuration)12.2 收集阶段 (Collection)12.3 运行阶段 (Running)12.4 总结阶段 (Summary) 12.2 钩子函数关键点 十三、Allure测试报告13.1 前言13.2 安装 Allure13.3 配置 pytest 以使用 Allure13.4 Allure装饰器函数13.5 执行自动化用例 生成allure报告所需文件13.6 查看测试报告的两种方式13.6.1 直接打开默认浏览器展示报告13.6.2 从结果生成报告 十四、pytest中管理日志14.1 日志级别14.2 使用logging模块14.3 将日志保存到文件14.4 控制日志捕获的行为14.5 示例 一、前言 pytest 是一个功能强大且易于使用的Python测试框架它允许开发者编写简单或复杂的函数式测试。pytest 的设计理念是让测试过程尽可能的简单和直观同时提供丰富的插件生态系统来扩展其功能。 介绍 易用性pytest 不需要额外的导入语句来标记测试函数如unittest中的test_前缀你可以直接使用标准的断言语句assert来进行测试这使得测试代码更加简洁、可读自动发现测试pytest 能够自动找到并运行测试。默认情况下它会查找文件名以test_开头或结尾的模块以及任何以Test开头的类无需继承自特定的父类。在这些模块或类中它会执行所有以test_开头的方法或函数参数化测试pytest 支持参数化测试这意味着你可以用不同的输入数据多次运行同一个测试函数而不需要为每个数据点编写单独的测试函数详细的报告和输出当测试失败时pytest 提供了清晰的错误信息和追踪帮助你快速定位问题所在丰富的插件生态pytest 拥有一个活跃的社区和大量的第三方插件可以用来增强测试的功能例如集成覆盖率报告、与CI/CD工具对接、支持异步测试等兼容其他测试框架pytest 可以运行unittest和nose风格的测试所以如果你有旧的测试代码通常可以直接使用pytest来运行它们而不需要重写内置的fixture机制pytest 引入了fixture的概念这是一个非常强大的特性用于设置前置条件比如创建数据库连接、初始化对象等并且可以在多个测试之间共享命令行选项pytest 提供了许多有用的命令行选项让你能够灵活地控制测试行为比如选择运行特定的测试、跳过某些测试、根据关键字筛选测试等等
二、安装
2.1 命令行安装
pip install pytest2.2 验证安装
pytest --version三、pytest设计测试用例注意点
3.1 命名规范
文件名文件名要以test_开头或结尾 例如test_login.py函数名函数名要以test_开头这样子有助与python自动取搜索他如果使用类来组织测试类名应以 Test 开头并且不应继承自任何特定的基类除非是为了使用某些特性
3.2 断言清晰
使用 Python 内置的 assert 语句来进行断言。pytest 会提供详细的失败信息因此尽量让断言语句尽可能直接明了
3.3 fixture
fixture是pytest中非常重要的概念用于设置测试环境我们要合理的使用fixture减少代码的重复使用提高测试效率
3.4 参数化设置
利用 pytest.mark.parametrize 装饰器可以为同一个测试函数提供多组输入数据从而避免编写多个类似的测试函数
3.5 测试隔离
使用setup和teardowm来准备测试环境
3.6 异常处理
如果你的测试预期某个操作会抛出异常可以使用 pytest.raises 上下文管理器来检查是否确实发生了预期的异常
3.7 跳过或者预期失败
对于暂时无法通过的测试可以使用 pytest.mark.skip 或 pytest.mark.xfail 标记以便在不影响整体测试结果的情况下继续开发
3.8 mocking
当测试需要依赖外部系统如数据库、网络服务等时考虑使用 unittest.mock 或者第三方库如 pytest-mock 来模拟这些依赖确保测试的快速性和稳定性
3.9 标记测试
使用 pytest.mark 可以为测试添加标签比如 slow, network, database 等然后可以根据这些标签选择性地运行测试
四、以案例初入pytest
4.1 第一个pytest测试
创建一个名为test_demo的文件名其中有一个函数 一个测试
def func(x):return x 1def test_answer():assert func(3) 5 # 断言在命令行输入pytest运行以下是输出结果
$ pytesttest session starts
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 itemtest_sample.py F [100%] FAILURES
_______________________________ test_answer ________________________________def test_answer():assert func(3) 5
E assert 4 5
E where 4 func(3)test_sample.py:6: AssertionErrorshort test summary info
FAILED test_sample.py::test_answer - assert 4 51 failed in 0.12s 4.2 多个测试分组一个类里面
创建一个名为test_demo的文件名创建一个类 其中有两个函数
class TestClass:def test_one(self):x thisassert h in xdef test_two(self):x helloassert hasattr(x, check) # 断言x是否具有名为check的属性或方法在命令行输入pytest运行以下是输出结果
$ pytest -q test_class.py
.F [100%]FAILURES
____________________________ TestClass.test_two ____________________________self test_class.TestClass object at 0xdeadbeef0001def test_two(self):x helloassert hasattr(x, check)
E AssertionError: assert False
E where False hasattr(hello, check)test_class.py:8: AssertionErrorshort test summary info
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s其中第一条是成功 第二条是失败 失败原因就是在x不具有check的属性 观察失败原因主要看断言中的中间值
4.3 将多个测试用例分组
好处 测试组织 仅在特定类中共享用于测试的装置 在班级层面上应用标记并让它们隐式地应用于所有测试 class TestClassDemoInstance:value 0def test_one(self):self.value 1assert self.value 1def test_two(self):assert self.value 1$ pytest -k TestClassDemoInstance -q
.F [100%]FAILURES
______________________ TestClassDemoInstance.test_two ______________________self test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002def test_two(self):assert self.value 1
E assert 0 1
E where 0 test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002.valuetest_class_demo.py:9: AssertionErrorshort test summary info
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 1
1 failed, 1 passed in 0.12s4.4 pytest运行中命令行运行可选参数
参数功能-v增加输出的详细程度-q减少输出信息-k EXPRESSION根据表达式选择运行哪些测试例如 -k ‘not slow’ 可以跳过标记为 slow 的测试-x遇到第一个失败就退出–htmlREPORT.html生成HTML格式的测试报告需要安装 pytest-html 插件–maxfailNUM在达到指定数量的失败后停止测试-m MARKEXPR只运行带有指定标记的测试例如 -m slow-n NUM 或 --numprocessesNUM使用多个进程并行运行测试需要安装 pytest-xdist 插件-s不捕获标准输出和错误输出允许直接看到 print 调用的结果–ignorepath忽略指定路径下的测试文件
五、配置文件pytest.ini pytest.ini 文件是 pytest 的配置文件之一用于定义项目的全局设置和选项。通过这个文件你可以定制化测试行为指定插件、命令行选项以及其他配置项而无需每次都手动在命令行中输入这些参数 [pytest]
# 基本配置选项
addopts -ra -q --tbshort
testpaths tests/
markers slow: marks tests as slow (deselect with -m not slow)serial: marks tests that should run in serial
python_files test_*.py *_test.py
python_classes Test* *Tests
python_functions test_*# 插件配置
plugins myplugin, otherplugin# 环境变量
env ENV_VARvalue1OTHER_ENV_VARvalue2# 代码覆盖率配置需要安装 pytest-cov
addopts --covmyproject --cov-reportterm-missing# 并行测试配置需要安装 pytest-xdist
addopts -n auto# 设置默认的编码为 utf-8
console_output_encoding utf-8
file_system_encoding utf-8# 设置收集器忽略某些路径
norecursedirs .git .tox dist build# 自定义日志格式需要安装 pytest-log-clerk 或类似插件
log_cli True
log_cli_level INFO
log_cli_format %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format %Y-%m-%d %H:%M:%S关键配置项解释
addopts添加默认的命令行选项。这可以包含任何有效的 pytest 命令行参数。例如-ra 表示显示所有错误摘要–tbshort 设置回溯输出风格testpaths指定要搜索测试的目录默认情况下 pytest 会递归搜索当前目录及其子目录中的所有匹配文件markers定义自定义标记的帮助信息使得其他开发者更容易理解标记的意义python_files指定哪些文件名模式被视为测试文件python_classes指定哪些类名模式被视为测试类python_functions指定哪些函数名模式被视为测试函数plugins加载额外的插件。通常不需要显式声明因为大多数插件会自动注册norecursedirs排除不希望递归搜索的目录log_cli 和相关日志配置控制命令行日志输出的行为需要适当的插件支持coverage 和并行测试配置可以通过 addopts 添加与 pytest-cov 或 pytest-xdist 相关的选项
六、conftest文件 conftest.py 文件是 Pytest 框架中的一个特殊文件用于包含 fixture固定装置和其他配置代码。Pytest 会自动加载名为 conftest.py 的文件中定义的 fixtures 和插件而不需要在测试模块中显式导入它们。这个文件通常用来存放那些被多个测试文件共享的配置和设置 6.1 conftest的关键点
位置conftest.py 文件应当放置在你的测试文件所在的目录或其父目录中。Pytest 会递归地查找这些文件作用域定义在 conftest.py 中的 fixture 可以被该文件所在目录及其子目录下的所有测试文件使用内容可以包含 fixtures、hooks钩子函数和其他配置选项。它不应该包含实际的测试代码命名文件名必须严格为 conftest.py否则 Pytest 将不会识别它初始化代码如果需要执行一些一次性的初始化代码比如设置日志记录、数据库连接等可以在 conftest.py 中定义
6.2 conftest案例
例如在 conftest.py 中定义一个 fixture用来作为登录模块用例的前置操作
import pytest
from seleium import webdirverpytest.fixture(scopeclass)
def login():driver webdriver.Chrome()driver.get(http://127.0.0.1)driver.maximzie_window()driver.implicitly_wait(10)yield driverdriver quit()七、mark属性标记 通过使用pytest.mark帮助程序您可以轻松地在测试函数上设置元数据 7.1 内置/自定义标记 usefixtures - 在测试函数或类上使用fixture filterwarnings-过滤测试函数的某些警告 skip-总是跳过测试函数 skipif - 如果满足某个条件则跳过测试函数 xfail - 如果满足某个条件则产生“预期失败”结果 参数化-对同一个测试函数执行多次调用
自定义标记
自定义标记就如上述pytest.ini文件自定义标记
[pytest]
markers slow: marks tests as slow (deselect with -m not slow)serial7.2 pytest.mark.parametrize参数化测试函数 pytest.mark.parametrize 是 pytest 框架提供的一个装饰器用于参数化测试函数。它允许你定义多个参数集然后针对每个参数集运行测试函数这样可以有效地减少代码重复并且使得测试更加灵活和易于维护 使用 pytest.mark.parametrize 装饰器时你需要提供两个参数
第一个参数是一个字符串其中包含逗号分隔的参数名列表第二个参数是一个元组列表或者可迭代的对象每个元组代表一组测试数据这些数据会依次传递给测试函数的相应参数
示例
import pytestdef add(x, y):return x ypytest.mark.parametrize(x, y, expected, [(1, 2, 3),(0, 5, 5),(-1, -1, -2),(3.2, 4.8, 8.0),
])
def test_add(x, y, expected):assert add(x, y) expected在这里parametrize 装饰器定义了四组x, y, expected元组以便teat_add依次运行得出四组结果
test_example.py::test_add[1-2-3] PASSED
test_example.py::test_add[0-5-5] PASSED
test_example.py::test_add[-1--1--2] PASSED
test_example.py::test_add[3.2-4.8-8.0] FAILED八、Fixture装饰器
8.1 基本概念
在 pytest 中fixture 是一种用于设置测试环境的机制。它们可以用来执行一些前置或后置操作例如准备数据、启动服务、清理状态等并且可以在多个测试之间共享。fixture 的设计使得代码复用和测试之间的依赖关系更加清晰同时也让测试函数本身保持简洁
8.2 Fixture方法解析
fixture方法
fixture(callable_or_scopeNone, *args, scopefunction, paramsNone, autouseFalse, idsNone, nameNone)scopefixture的作用域默认为function;autouse默认False需要用例手动调用该fixture如果是True所有作用域内的测试用例都会自动调用该fixture;name装饰器的名称同一模块的fixture相互调用建议写不同的name
作用域scope
(scope)决定了 fixture 的生命周期function (默认)每个测试函数调用一次class每个测试类调用一次module每个模块加载时调用一次session整个测试会话期间只调用一次
参数化params
可以为 fixture 提供参数类似于参数化测试
自动应用autouse
如果一个测试函数需要某个 fixturepytest 会自动调用它无需显式地传递
依赖注入request
一个 fixture 可以依赖于另一个 fixture并通过参数传递来实现这种依赖关系
8.3 Fixture的创建和使用
你可以通过装饰器 pytest.fixture 来定义一个 fixture 函数。下面是一个简单的例子
import pytestpytest.fixture
def sample_data():# 前置操作比如初始化数据data {value: 42}yield data# 后置操作比如清理资源print(Cleanup after test)要使用fixture只需要将其作为参数传递给测试函数
def test_with_fixture(sample_data):assert sample_data[value] 42在这个例子中sample_data 是一个 fixture 函数它会在测试 test_with_fixture 运行之前被调用提供了一个包含特定数据的字典给测试函数。yield 关键字之后的代码是后置操作在测试完成后执行
8.4 调用Fixture的方式
方式一直接作为测试函数的参数 这是最常见和推荐的方式。你只需要将 fixture 名称作为参数传递给测试函数或类的方法pytest 就会自动为你调用该 fixture
import pytestpytest.fixture
def sample_data():print(Setting up fixture)return {value: 42}def test_with_fixture(sample_data):print(fTesting with data: {sample_data})assert sample_data[value] 42执行结果 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 1 itemtest_example.py::test_with_fixture Setting up fixture
Testing with data: {value: 42}
PASSED 1 passed in X.XX seconds 对于类中的方法也可以同样地使用
class TestClass:def test_method(self, sample_data):assert sample_data[value] 42方式二使用 pytest.mark.usefixtures 标记 如果你不想在每个测试函数中都列出所有的 fixtures或者你需要为多个测试函数应用同一个 fixture可以使用 pytest.mark.usefixtures 来标记这些测试函数或整个测试类
import pytestpytest.fixture
def setup():print(Setup fixture called)yieldprint(Teardown fixture called)pytest.fixture
def another_setup():print(\nAnother setup fixture called)yieldprint(Teardown another setup fixture called)pytest.mark.usefixtures(setup)
def test_one():print(Test one running)pytest.mark.usefixtures(setup)
def test_two():print(Test two running)执行结果 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 2 itemstest_example.py::test_one Setup fixture called
Test one running
PASSED
Teardown fixture calledtest_example.py::test_two Setup fixture called
Test two running
PASSED
Teardown fixture called 2 passed in X.XX seconds 你也可以一次性为多个测试函数或整个测试类添加多个 fixtures
pytest.mark.usefixtures(setup, another_setup)
class TestClass:def test_method(self):passdef test_another_method(self):pass执行结果 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 2 itemstest_example.py::TestClass::test_method
Setup fixture calledAnother setup fixture calledTest method running
PASSED
Teardown another setup fixture called
Teardown setup fixture calledtest_example.py::TestClass::test_another_method
Setup fixture calledAnother setup fixture calledTest another method running
PASSED
Teardown another setup fixture called
Teardown setup fixture called 2 passed in X.XX seconds 方式三自动应用 (autouseTrue) 当你定义一个 fixture 时可以通过设置 autouseTrue 参数使其自动应用于所有测试函数而不需要显式地将其作为参数传递或使用 pytest.mark.usefixtures 标记
import pytestpytest.fixture(autouseTrue)
def always_used_fixture():print(This fixture is automatically applied to all tests.)def test_without_explicit_dependency():print(Running a test without explicitly depending on the fixture.)执行结果 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 1 itemtest_example.py::test_with_autouse Setting up autouse fixture
Setting up sample_data fixture
Testing with autouse and sample_data fixture data: {value: 42}
PASSED 1 passed in X.XX seconds 九 、pytest跳过测试用例方法
9.1 skip – 跳过测试用例 您可以标记无法在某些平台上运行或预计会失败的测试功能以便 pytest 可以相应地处理它们并提供测试会话的摘要同时保持测试套件为绿色 跳过意味着您仅希望测试在满足某些条件时才能通过否则 pytest 应完全跳过运行测试。常见示例是在非 Windows 平台上跳过仅限 Windows 的测试或跳过依赖于当前不可用的外部资源例如数据库的测试
使用 pytest.mark.skip 装饰器 pytest.mark.skip() 通常用于在定义测试函数时标记该函数应该被跳过而不是在函数内部使用 如果你想在定义测试函数时提供跳过的原因可以使用带有 reason 参数的 pytest.mark.skip 装饰器 import pytestpytest.mark.skip(reasonThis test is skipped because its not ready yet.)
def test_skip_with_reason():print(This test should be skipped and you should see the reason why.)使用 pytest.skip() 在函数内部跳过 如果你需要根据某些运行时条件来决定是否跳过测试可以在测试函数内部使用 pytest.skip() 函数 import pytestdef test_skip_inside_function():condition False # 这里可以是任何条件判断if not condition:pytest.skip(Skipping this test based on a runtime condition.)print(This part of the test will only run if the condition is True.)结合两种方法 下面是一个完整的例子展示了如何使用 pytest.mark.skip 和 pytest.skip() import pytest# 使用装饰器跳过测试并提供原因
pytest.mark.skip(reasonThis test is not implemented yet.)
def test_skip_with_reason():print(This test should be skipped.)# 根据条件在函数内部跳过测试
def test_skip_inside_function():condition False # 这里可以是任何条件判断if not condition:pytest.skip(Skipping this test based on a runtime condition.)print(This part of the test will only run if the condition is True.)# 正常测试用例作为对比
def test_normal_case():print(Running a normal test case.)assert True执行结果
# 输入
pytest -v -s test_example.py# 结果test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 3 itemstest_example.py::test_skip_with_reason SKIPPED (This test is not implemented yet.)
test_example.py::test_skip_inside_function
Skipping this test based on a runtime condition.
SKIPPED
test_example.py::test_normal_case Running a normal test case.
PASSED 1 passed, 2 skipped in X.XX seconds 9.2 skipif – 有条件跳过测试用例
pytest.mark.skipif 是 pytest 提供的一个装饰器用于根据给定条件跳过测试用例。如果提供的条件为 True则该测试将被跳过如果条件为 False则测试会正常运行。你可以通过传递一个布尔表达式和一个可选的 reason 参数来解释为什么跳过测试
使用 pytest.mark.skipif
import pytest# 如果条件为 True则跳过测试
pytest.mark.skipif(True, reasonThis test is skipped because the condition is True.)
def test_skipif_with_true_condition():print(This test should be skipped.)# 如果条件为 False则测试不会被跳过
pytest.mark.skipif(False, reasonThis test will not be skipped because the condition is False.)
def test_skipif_with_false_condition():print(This test should run.)依赖外部条件 通常你会使用 skipif 来检查一些外部条件比如环境变量、操作系统类型或第三方库的存在等 以下是在 Python3.8 之前的解释器上运行时标记要跳过的测试函数的示例
import sys
import pytest# 根据 Python 版本跳过测试
pytest.mark.skipif(sys.version_info (3, 8), reasonRequires Python 3.8 or higher)
def test_requires_python_38():print(Running a test that requires Python 3.8 or higher.)结合多个条件 你还可以将多个条件组合起来或者在 fixture 中使用 skipif
import pytest# 定义一个 fixture它可以根据条件跳过所有使用它的测试
pytest.fixture
def check_environment():if some_condition: # 替换为实际条件判断pytest.skip(Skipping due to environment configuration.)# 使用 fixture 的测试函数
def test_with_check_environment(check_environment):print(This test runs only if the environment check passes.)# 结合多个条件
pytest.mark.skipif(sys.platform win32 and sys.version_info (3, 8),reasonThis test requires Python 3.8 or higher on Windows.
)
def test_combined_conditions():print(Running a test with combined conditions.)输出结果 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 3 itemstest_example.py::test_with_check_environment
This test runs only if the environment check passes.
PASSED
test_example.py::test_combined_conditions
Running a test with combined conditions.
PASSED
test_example.py::test_normal_case
Running a normal test case.
PASSED 3 passed in X.XX seconds 十、失败重跑
10.1 使用 --last-failed 选项
这个选项会只运行上一次测试会话中失败的测试用例而跳过所有通过的测试用例。这对于快速重新运行失败的测试非常有用
案例 首先让我们创建 50 个测试调用其中只有 2 个失败
# content of test_50.py
import pytestpytest.mark.parametrize(i, range(50))
def test_num(i):if i in (17, 25):pytest.fail(bad luck)如果您第一次运行该程序您将看到两个失败
$ pytest -q
.................F.......F........................ [100%]FAILURES
_______________________________ test_num[17] _______________________________i 17pytest.mark.parametrize(i, range(50))def test_num(i):if i in (17, 25):pytest.fail(bad luck)
E Failed: bad lucktest_50.py:7: Failed
_______________________________ test_num[25] _______________________________i 25pytest.mark.parametrize(i, range(50))def test_num(i):if i in (17, 25):pytest.fail(bad luck)
E Failed: bad lucktest_50.py:7: Failedshort test summary info
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck
2 failed, 48 passed in 0.12s如果你使用以下命令运行它–lf
$ pytest --lftest session starts
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
run-last-failure: rerun previous 2 failurestest_50.py FF [100%] FAILURES
_______________________________ test_num[17] _______________________________i 17pytest.mark.parametrize(i, range(50))def test_num(i):if i in (17, 25):pytest.fail(bad luck)
E Failed: bad lucktest_50.py:7: Failed
_______________________________ test_num[25] _______________________________i 25pytest.mark.parametrize(i, range(50))def test_num(i):if i in (17, 25):pytest.fail(bad luck)
E Failed: bad lucktest_50.py:7: Failedshort test summary info
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck2 failed in 0.12s 10.2 使用 --failed-first 选项
这个选项会在测试会话开始时首先运行上次失败的测试用例然后再运行其他的测试用例。这有助于尽早发现问题并且可以继续运行其他测试以确保没有引入新的问题
案例 我们将编写三个测试函数两个会成功一个会失败。然后我们将运行这些测试并在修复失败的测试后再次运行它们以显示 --failed-first 的效果
import pytestdef test_success_one():print(Running test_success_one)assert Truedef test_success_two():print(Running test_success_two)assert Truedef test_failure():print(Running test_failure)assert False, This test is supposed to fail.第一步初次运行测试 首先我们运行所有测试来确定哪些测试失败了 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 3 itemstest_example.py::test_success_one Running test_success_one
PASSED
test_example.py::test_success_two Running test_success_two
PASSED
test_example.py::test_failure Running test_failure
FAILED FAILURES
_______________________________ test_failure _________________________________def test_failure():print(Running test_failure)assert False, This test is supposed to fail.
E AssertionError: This test is supposed to fail.
E assert Falsetest_example.py:10: AssertionErrorshort test summary info
FAILED test_example.py::test_failure - AssertionError: This test is supposed to fail.2 passed, 1 failed in X.XX seconds 第二步修复失败的测试 现在我们修复 test_failure 函数中的错误
def test_failure():print(Running test_failure (fixed))assert True, This test has been fixed.第三步使用 --failed-first 重新运行测试 接下来我们使用 --failed-first 选项来确保上次失败的测试优先运行。这有助于尽早发现问题是否已经被解决 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 3 itemstest_example.py::test_failure Running test_failure (fixed)
PASSED
test_example.py::test_success_one Running test_success_one
PASSED
test_example.py::test_success_two Running test_success_two
PASSED 3 passed in X.XX seconds 10.3 使用 pytest-rerunfailures 插件
如果你需要在同一个测试会话中多次重试失败的测试可以安装并使用 pytest-rerunfailures 插件。这个插件允许你指定一个次数当测试失败时它会自动重试指定的次数
安装插件
pip install pytest-rerunfailures安装前提
pytest(5.3) and python3.6查看安装版本
pip show pytest-rerunfailurespytest-rerunfailures方法使用
命令行参数-reruns n(重新运行次数) - rerruns -delay m (等待运行次数)
使用装饰器
pytest.mark.flaky(reruns5,reruns_delay2)
命令行案例 我们将编写三个测试函数两个会成功一个会失败。然后我们将运行这些测试并在修复失败的测试后再次运行它们以显示 pytest-rerunfailures 如何工作
import pytestdef test_success_one():print(Running test_success_one)assert Truedef test_success_two():print(Running test_success_two)assert Truedef test_failure():print(Running test_failure)# 这个断言会在第一次执行时失败但在后续重试中通过if not hasattr(test_failure, retry_count):test_failure.retry_count 0test_failure.retry_count 1if test_failure.retry_count 3:assert False, fThis test is supposed to fail on retry {test_failure.retry_count}else:print(This test has been fixed and now passes.)assert True第一步初次运行测试 首先我们运行所有测试来确定哪些测试失败了并查看重试机制是否按预期工作
执行命令
pytest --reruns 3 --reruns-delay 1 -v -s test_example.py这里–reruns 3 表示每个失败的测试最多重试 3 次–reruns-delay 1 表示每次重试之间等待 1 秒
预期结果 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 3 itemstest_example.py::test_success_one Running test_success_one
PASSED
test_example.py::test_success_two Running test_success_two
PASSED
test_example.py::test_failure Running test_failure
FAILED---------------------------------- Captured stdout call ----------------------------------
Running test_failure FAILURES
_______________________________ test_failure _________________________________def test_failure():print(Running test_failure)if not hasattr(test_failure, retry_count):test_failure.retry_count 0test_failure.retry_count 1if test_failure.retry_count 3:assert False, fThis test is supposed to fail on retry {test_failure.retry_count}
E AssertionError: This test is supposed to fail on retry 1
E assert Falsetest_example.py:16: AssertionError
----------------------------- RERUN test_failure ------------------------------
test_example.py::test_failure (re-run 1) Running test_failure
FAILED---------------------------------- Captured stdout call ----------------------------------
Running test_failure FAILURES
_______________________________ test_failure _________________________________def test_failure():print(Running test_failure)if not hasattr(test_failure, retry_count):test_failure.retry_count 0test_failure.retry_count 1if test_failure.retry_count 3:assert False, fThis test is supposed to fail on retry {test_failure.retry_count}
E AssertionError: This test is supposed to fail on retry 2
E assert Falsetest_example.py:16: AssertionError
----------------------------- RERUN test_failure ------------------------------
test_example.py::test_failure (re-run 2) Running test_failure
PASSED---------------------------------- Captured stdout call ----------------------------------
Running test_failure
This test has been fixed and now passes. short test summary info
FAILED test_example.py::test_failure - AssertionError: This test is supposed to fail on retry 1
FAILED test_example.py::test_failure (re-run 1) - AssertionError: This test is supposed to fail on retry 2
PASSED test_example.py::test_failure (re-run 2)2 passed, 1 failed in X.XX seconds 在这个输出中我们可以看到 test_failure 在前两次重试中失败了但在第三次重试中通过了。
总结
初次运行展示了哪些测试通过了哪些失败了并且展示了重试机制重试机制test_failure 测试在前两次重试中失败但在第三次重试中通过了使用 pytest-rerunfailures 插件确保失败的测试可以在同一个测试会话中多次重试从而减少由于环境或其他不稳定因素导致的假阳性失败
十一、pytest执行顺序 在 pytest 中测试函数的执行顺序默认是按照它们在文件中的定义顺序。然而有时候你可能希望控制测试的执行顺序例如确保某些依赖关系得以满足或优化测试运行时间。pytest 提供了多种方式来控制测试执行顺序包括使用 pytest.mark.order 装饰器需要安装 pytest-ordering 插件和内置的 pytest-order 插件 安装插件 pip install pytest-order
案例 我们将创建几个测试函数并使用 pytest.mark.order 来指定它们的执行顺序
import pytestpytest.mark.order(2)
def test_second():print(Running second test)assert Truepytest.mark.order(1)
def test_first():print(Running first test)assert Truepytest.mark.order(3)
def test_third():print(Running third test)assert Truedef test_unordered():print(Running unordered test)assert True在这个例子中我们指定了三个测试的执行顺序test_first 会最先运行然后是 test_second最后是 test_third。test_unordered 没有指定顺序因此它将根据其在文件中的位置决定执行顺序通常是在所有有序测试之后执行
预期结果 test session starts
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /path/to/your/project
collected 4 itemstest_example.py::test_first Running first test
PASSED
test_example.py::test_second Running second test
PASSED
test_example.py::test_third Running third test
PASSED
test_example.py::test_unordered Running unordered test
PASSED 4 passed in X.XX seconds 在这个输出中可以看到测试按照我们指定的顺序执行test_first - test_second - test_third而 test_unordered 在最后执行
不同的排序策略
除了使用数字来指定顺序外pytest-order 还支持其他排序策略比如按字母顺序、反向顺序等。你还可以结合多个装饰器来实现更复杂的排序逻辑。
示例按字母顺序执行
如果你想要按字母顺序执行测试可以使用 pytest.mark.order(“alphabetical”)
pytest.mark.order(alphabetical)
def test_a():print(Running test_a)assert Truepytest.mark.order(alphabetical)
def test_b():print(Running test_b)assert True十二、hooks钩子函数
12.1 钩子函数的四个阶段
12.1.1 配置阶段 (Configuration)
描述
在这个阶段pytest 解析命令行参数、读取配置文件并进行必要的初始化工作。你可以通过这个阶段的钩子函数来添加自定义选项或修改全局配置
常用钩子函数
pytest_addoption(parser): 用途向命令行接口添加自定义选项示例
def pytest_addoption(parser):parser.addoption(--runslow, actionstore_true, helprun slow tests)pytest_configure(config): 用途在所有测试开始前进行全局配置比如注册 markers 或设置其他全局状态。示例
def pytest_configure(config):config.addinivalue_line(markers, slow: mark test as slow to run)12.2 收集阶段 (Collection)
描述
pytest 在此阶段会搜索并收集所有符合标准的测试项test items。你可以在这一阶段修改哪些测试会被收集或者改变它们的属性。
常用钩子函数
pytest_collect_file(path, parent): 用途自定义文件收集器允许 pytest 收集非标准测试文件示例
def pytest_collect_file(parent, path):if path.ext .yaml and path.basename.startswith(test_):return YamlFile.from_parent(parent, fspathpath)pytest_collection_modifyitems(session, config, items): 用途修改收集到的测试项列表例如根据条件过滤或重新排序测试项。示例
def pytest_collection_modifyitems(items):items.sort(keylambda item: item.name) # 按名称排序12.3 运行阶段 (Running)
描述
这是实际执行测试的阶段。pytest 会依次调用每个测试项的 setup、call 和 teardown 方法。你可以在这些方法中插入额外的逻辑如日志记录、性能监控等
常用钩子函数
pytest_runtest_protocol(item, nextitem): 用途控制整个测试协议包括 setup、call 和 teardown。 pytest_runtest_setup(item): 用途在每个测试项执行之前调用用于设置测试环境。示例
def pytest_runtest_setup(item):print(fSetting up {item.name})pytest_runtest_call(item): 用途在每个测试项的实际执行过程中调用。示例
def pytest_runtest_call(item):print(fCalling {item.name})pytest_runtest_teardown(item, nextitem): 用途在每个测试项执行之后调用用于清理测试环境。示例
def pytest_runtest_teardown(item, nextitem):print(fTearing down {item.name})pytest_report_teststatus(report, config): 用途自定义测试状态报告改变测试通过、失败或跳过的显示方式。示例
def pytest_report_teststatus(report, config):if report.when call and report.failed:return failed, F, FAILED12.4 总结阶段 (Summary)
描述
在所有测试完成后pytest 会生成一个总结报告显示测试结果。你可以在此阶段添加自定义的总结信息或者修改默认的输出格式。
常用钩子函数
pytest_terminal_summary(terminalreporter, exitstatus, config): 用途在终端输出总结信息。示例
def pytest_terminal_summary(terminalreporter, exitstatus, config):print(Custom summary information)四个阶段的关系 配置阶段为测试会话准备环境确保一切就绪收集阶段确定哪些测试需要运行并构建测试项列表运行阶段依次执行测试项并处理每个测试的 setup、call 和 teardown总结阶段提供测试结果的汇总信息并结束测试会话
12.2 钩子函数关键点
1. 调用顺序了解各个阶段的钩子函数调用顺序以便在适当的时间点插入逻辑 2. 常见钩子函数熟悉关键的钩子函数及其用途以实现所需的定制化功能 3. request 对象利用 request 对象提供的上下文信息来增强灵活性
许多钩子函数接收一个 request 对象作为参数该对象提供了访问当前测试上下文的能力。request 对象非常强大因为它包含了关于测试会话、节点、配置等方面的信息
request.config: 访问全局配置request.node: 获取当前测试项的信息request.addfinalizer(): 注册一个函数在测试结束时调用 4. 插件兼容性确保自定义插件与现有插件良好协作 5.文档和社区支持充分利用官方文档和社区资源来解决问题和学习最佳实践 结合案例
假设你想确保一些测试总是最先运行而另一些则在最后运行。你可以结合 pytest_collection_modifyitems 和 pytest.mark.order 来实现这一点
import pytestdef pytest_collection_modifyitems(items):# 定义一个排序键确保带有 order 标记的测试按照指定顺序执行items.sort(keylambda item: (getattr(item.get_closest_marker(order), args, [0])[0], item.name))pytest.mark.order(1)
def test_first():print(Running first test)assert Truepytest.mark.order(2)
def test_second():print(Running second test)assert Truedef test_unordered():print(Running unordered test)assert True在这个例子中pytest_collection_modifyitems 确保了标记为 pytest.mark.order 的测试按照指定顺序执行而未标记的测试则排在其后
十三、Allure测试报告
13.1 前言
Allure 是一个灵活且功能强大的测试报告工具支持多种编程语言和测试框架包括 Python 的 pytest。它能够生成详细且美观的测试报告帮助团队更好地理解和分析测试结果。以下是关于如何在 pytest 中集成 Allure 测试报告的关键点和步骤
13.2 安装 Allure
首先你需要安装 Allure 和相关插件
安装 Allure 命令行工具 使用 HomebrewMacOSbrew install allure使用 ChocolateyWindowschoco install allure或者从 下载并手动安装。 安装 pytest-allure-adaptor 插件 使用 pip 安装pip install pytest-allure-adaptor
13.3 配置 pytest 以使用 Allure 1. 在命令行中启用 Allure 你可以直接在命令行中通过添加 --alluredir 参数来指定保存 Allure 结果的目录
pytest --alluredir/path/to/result/dir2. 使用 pytest.ini 或 tox.ini 配置文件
你也可以将 Allure 配置添加到 pytest.ini 或 tox.ini 文件中以便每次运行测试时自动应用
[pytest]
addopts --alluredirallure-results13.4 Allure装饰器函数
装饰器函数
方法参数参数说明allure.epic()epic描述定义项目、当有多个项目是使用。往下是featureallure.feature()模块名称用例按照模块区分有多个模块时给每个起名字allure.story()用例名称一个用例的描述allure.title(用例的标题)用例标题一个用例的标题allure.testcase()测试用例连接的地址自动化用例对应的功能用例存放系统的地址allure.issue()缺陷地址对应缺陷管理系统里边的缺陷地址allure.description()用例描述对应测试用例的描述allure.step()测试步骤测试用例的操作步骤allure.severity()用例等级blocker 、critical 、normal 、minor 、trivialallure.link()定义连接用于定义一个需要在测试报告中展示的连接allure.attachment()附件添加测试报告附件
13.5 执行自动化用例 生成allure报告所需文件
测试代码
import pytestdef test_success():this test succeedsassert Truedef test_failure():this test failsassert Falsedef test_skip():this test is skippedpytest.skip(for a reason!)def test_broken():raise Exception(oops)
运行
pytest --alluredir./results13.6 查看测试报告的两种方式
13.6.1 直接打开默认浏览器展示报告
allure serve ./result/13.6.2 从结果生成报告 生成报告 allure generate ./result/ -o ./report/ --clean (覆盖路径加–clean) 打开报告 allure open -h 127.0.0.1 -p 8883 ./report/
十四、pytest中管理日志
14.1 日志级别
debug打印全部日志详细信息 info打印info、warning、error、critical级别的日志确认一切按预期运行 warning打印warning、error、critical级别的日志 error打印error、critical级别日志或者一些更为严重软件没能执行一些功能 critical打印critical日志一个严重的错误表明程序可能无法正常的执行
等级顺序
debug–》info–》warning–》error–》critical
14.2 使用logging模块
你可以直接在测试代码中使用 logging 模块来记录信息。pytest 会自动捕获这些日志并根据上述配置进行处理
import loggingdef test_example():logger logging.getLogger(__name__)logger.info(This is an info message)logger.debug(This is a debug message)assert True14.3 将日志保存到文件
有时你可能希望将日志保存到文件而不是仅限于终端输出。你可以通过配置 logging 模块来实现这一点
import logging# 配置日志记录器以写入文件
logging.basicConfig(filenametest.log, filemodew, levellogging.INFO)def test_logging_to_file():logger logging.getLogger(__name__)logger.info(Logging to file)assert True此外你也可以在 pytest.ini 中配置日志输出到文件
[pytest]
log_file test.log
log_file_level INFO14.4 控制日志捕获的行为
有时候你可能不想捕获某些特定的日志输出或者想完全禁用日志捕获。你可以通过 caplog fixture 来控制日志捕获的行为
def test_control_log_capture(caplog):caplog.set_level(logging.WARNING) # 只捕获 WARNING 级别及以上的日志logging.info(This will not be captured)logging.warning(This will be captured)assert captured in caplog.text14.5 示例
import logging
import os
import timefrom config.conf import BASE_DIR
import colorloglog_color_config {DEBUG: cyan,INFO: green,WARNING: yellow,ERROR: red,CRITICAL: bold_red}log logging.getLogger(log_name)consloe_handler logging.StreamHandler()
daytime time.strftime(%Y-%m-%d)
path BASE_DIR log/if not os.path.exists(path):os.makedirs(path)filename path f/run_log_{daytime}.logfile_handle logging.FileHandler(filenamefilename, modea, encodingutf-8)log.setLevel(logging.DEBUG)
consloe_handler.setLevel(logging.DEBUG)
file_handle.setLevel(logging.INFO)file_formatter logging.Formatter(fmt%(asctime)s - %(levelname)s - %(name)s - %(module)s:%(funcName)s:%(lineno)d - %(message)s,datefmt%Y-%m-%d %H:%M:%S)console_formatter colorlog.ColoredFormatter(fmt%(log_color)s%(levelname)-8s%(reset)s | %(log_color)s%(message)s%(reset)s | %(white)s%(asctime)s | %(blue)s%(name)s:%(filename)s:%(lineno)d%(reset)s,datefmt%Y-%m-%d %H:%M:%S, # 设置日期/时间格式 resetTrue, # 自动重置颜色到默认值 log_colorslog_color_config, # 使用上面定义的日志等级颜色配置 secondary_log_colors{}, # 可选为特定字段添加颜色 style% # 使)
consloe_handler.setFormatter(console_formatter)
file_handle.setFormatter(file_formatter)if not log.handlers:log.addHandler(consloe_handler)log.addHandler(file_handle)consloe_handler.close()
file_handle.close()if __name__ __main__:log.debug(debug)log.info(info)log.warning(warning)log.critical(critical)