02-单元测试
本章讲解 Python 单元测试的最佳实践。
pytest 基础
安装与使用
bash
# 安装
uv add --dev pytest pytest-cov
# 运行所有测试
pytest
# 运行特定测试文件
pytest tests/test_module.py
# 显示覆盖率
pytest --cov=src/my_package --cov-report=html编写测试
python
# tests/test_calculator.py
import pytest
from src.my_package.calculator import add, divide
def test_add():
"""测试加法"""
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_divide_by_zero():
"""测试异常"""
with pytest.raises(ZeroDivisionError):
divide(10, 0)参数化测试
python
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 6),
(5, 5, 25),
(0, 10, 0),
])
def test_multiply(a, b, expected):
assert multiply(a, b) == expectedFixtures
基础 Fixture
python
# tests/conftest.py
import pytest
from src.my_package.database import Database
@pytest.fixture
def db():
"""创建数据库连接"""
db = Database(":memory:")
yield db
db.close()python
# tests/test_database.py
def test_create_user(db, sample_user):
"""测试创建用户"""
service = UserService(db)
user = service.create(sample_user)
assert user["name"] == "Alice"Fixture 作用域
python
@pytest.fixture(scope="function") # 每个测试函数执行一次
def func_fixture():
return "function"
@pytest.fixture(scope="module") # 每个模块执行一次
def module_fixture():
return "module"
@pytest.fixture(scope="session") # 整个会话执行一次
def session_fixture():
return "session"Mock
使用 mock
python
from unittest.mock import Mock, patch
def test_api_client():
"""测试 API 客户端"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"temp": 25}
with patch('requests.get', return_value=mock_response):
client = APIClient()
result = client.get_weather("Beijing")
assert result["temp"] == 25测试覆盖率
配置
toml
# pyproject.toml
[tool.coverage.run]
source = ["src/my_package"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
]运行报告
bash
# HTML 报告
pytest --cov=src/my_package --cov-report=html
# 终端报告
pytest --cov=src/my_package --cov-report=term-missing最佳实践
AAA 模式
python
def test_withdraw_money():
"""Arrange-Act-Assert 模式"""
# Arrange - 准备
account = BankAccount(balance=100)
# Act - 执行
account.withdraw(50)
# Assert - 断言
assert account.balance == 50测试命名
python
# ✅ 清晰的测试名
def test_create_user_with_valid_email():
pass
def test_login_fails_with_invalid_password():
pass本章小结
┌─────────────────────────────────────────────────────────────┐
│ 单元测试 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ pytest: │
│ ✓ 使用 assert 断言 │
│ ✓ 参数化测试减少重复 │
│ │
│ Fixtures: │
│ ✓ conftest.py 共享 fixtures │
│ ✓ 使用 yield 清理资源 │
│ │
│ Mock: │
│ ✓ patch 替换外部依赖 │
│ ✓ 隔离测试单元 │
│ │
└─────────────────────────────────────────────────────────────┘