Skip to content

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) == expected

Fixtures

基础 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 替换外部依赖                                      │
│   ✓ 隔离测试单元                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘