前言
测试是软件质量的护城河,而测试工程化则是让这条护城河持续有效的关键。
在Python生态中,pytest几乎是单元测试的代名词——它的设计哲学是「简单用例简单写,复杂用例也有优雅写法」,零配置即能运行,同时也支持从基本到高级的各类复杂场景。
本文将带你从pytest基础出发,覆盖:
- 测试组织与 fixtures:参数化、共享设置、作用域控制
- Mock与MonkeyPatch:隔离外部依赖
- 断言的艺术:更精确的错误诊断
- 覆盖率与CI集成:从代码覆盖到质量门禁
- 测试工程化实践:大型项目的测试架构
目录结构
- 一、pytest基础:超越 unittest 的简洁
- 二、Fixtures:测试固件的高级玩法
- 三、Mock、Patch与MonkeyPatch:外部依赖隔离
- 四、参数化测试:一条用例覆盖多场景
- 五、跳过与条件执行:灵活控制测试行为
- 六、pytest插件生态:让测试更强大
- 七、测试工程化实践
- 八、总结
一、pytest基础:超越 unittest 的简洁
1.1 pytest vs unittest
# unittest 风格(标准库) import unittest class TestMathUtils(unittest.TestCase): def setUp(self): self.calc = Calculator() def test_add(self): self.assertEqual(self.calc.add(2, 3), 5) def test_divide_by_zero(self): with self.assertRaises(ZeroDivisionError): self.calc.divide(1, 0)# pytest 风格(更简洁,无类也可以) import pytest def add(a, b): return a + b def divide(a, b): if b == 0: raise ZeroDivisionError("除数不能为零") return a / b def test_add(): assert add(2, 3) == 5 def test_divide(): assert divide(10, 2) == 5.0 def test_divide_by_zero(): with pytest.raises(ZeroDivisionError, match="除数不能为零"): divide(1, 0)运行方式:
# 运行当前目录下所有测试 pytest # 运行指定文件 pytest tests/test_main.py # 运行指定测试函数 pytest tests/test_main.py::test_add # 详细输出 pytest -v # 显示print输出 pytest -s # 立即失败(不运行后续测试) pytest -x # 只运行上次失败的测试 pytest --lf # 对比两次运行的差异 pytest --lf --cache-show1.2 目录结构与conftest.py
pytest的约定优于配置原则体现在目录结构上:
myproject/ ├── src/ │ └── myproject/ │ ├── __init__.py │ ├── calculator.py │ └── user.py ├── tests/ │ ├── __init__.py │ ├── conftest.py ← 共享 fixtures(整个tests目录生效) │ ├── unit/ │ │ ├── __init__.py │ │ ├── conftest.py ← 单元测试共享 fixtures │ │ └── test_calculator.py │ ├── integration/ │ │ ├── __init__.py │ │ ├── conftest.py ← 集成测试共享 fixtures │ │ └── test_api.py │ └── fixtures/ │ ├── __init__.py │ └── sample_data.py ← 测试数据模块 └── pyproject.toml1.3 测试发现规则
pytest自动发现测试文件的规则:
| 模式 | 描述 |
|---|---|
test_*.py | 文件名以test_开头 |
*_test.py | 文件名以_test结尾 |
tests/目录 | tests目录下的所有.py |
Test*类 | 类名以Test开头(不含__init__的类) |
test_*函数 | 函数名以test_开头 |
*_test函数 | 函数名以_test结尾 |
# 测试类发现规则(类内方法也需要test_开头) class TestCalculator: def test_add(self): assert True def not_a_test(self): # 不会被发现 pass二、Fixtures:测试固件的高级玩法
Fixtures是pytest最强大的特性之一,用于提供测试所需的依赖和数据。
2.1 基础fixture
# conftest.py import pytest @pytest.fixture def sample_