前置知识: 软件测试

pytest

3 minIntermediate2026/6/14

pytest单元测试框架:fixture、参数化、插件、配置与最佳实践详解。

1. pytest 基础

1.1 安装

pip install pytest pytest-cov pytest-mock

1.2 基本示例

# test_calc.py
def add(a, b):
    return a + b

def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0
    assert add(0, 0) == 0
pytest test_calc.py -v

1.3 测试发现规则

规则描述
文件名test_*.py*_test.py
Test*(无 __init__
函数名test_*

2. Fixture

2.1 基本用法

import pytest

@pytest.fixture
def sample_data():
    return [1, 2, 3, 4, 5]

def test_sum(sample_data):
    assert sum(sample_data) == 15

2.2 Fixture 作用域

作用域描述
function每个测试函数(默认)
class每个测试
module每个模块
session整个测试会话
@pytest.fixture(scope="session")
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()

2.3 Fixture 依赖

@pytest.fixture
def db(db_connection):
    return Database(db_connection)

@pytest.fixture
def user(db):
    return db.create_user(name="Alice")

2.4 conftest.py

# conftest.py - 自动发现,无需导入
import pytest

@pytest.fixture
def app():
    app = create_app()
    app.config["TESTING"] = True
    return app

@pytest.fixture
def client(app):
    return app.test_client()

2.5 参数化 Fixture

@pytest.fixture(params=["sqlite", "postgresql"])
def db_engine(request):
    engine = create_engine(request.param)
    yield engine
    engine.dispose()

def test_query(db_engine):
    result = db_engine.execute("SELECT 1")
    assert result.fetchone()[0] == 1

3. 参数化测试

3.1 @pytest.mark.parametrize

@pytest.mark.parametrize("input,expected", [
    (1, 1),
    (2, 4),
    (3, 9),
    (-1, 1),
    (0, 0),
])
def test_square(input, expected):
    assert input ** 2 == expected

3.2 多参数组合

@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [10, 20])
def test_multiply(x, y):
    assert x * y > 0

3.3 参数化 ID

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("WORLD", "WORLD"),
], ids=["lowercase", "uppercase"])
def test_upper(input, expected):
    assert input.upper() == expected

4. 标记(Markers)

4.1 内置标记

标记描述
@pytest.mark.skip跳过测试
@pytest.mark.skipif条件跳过
@pytest.mark.xfail预期失败
@pytest.mark.parametrize参数化
@pytest.mark.slow自定义标记

4.2 自定义标记

# pytest.ini
[pytest]
markers =
    slow: slow tests
    integration: integration tests

# 使用
@pytest.mark.slow
def test_large_dataset():
    ...

4.3 选择执行

# 运行非 slow 测试
pytest -m "not slow"

# 运行 integration 测试
pytest -m integration

# 组合
pytest -m "integration and not slow"

5. Mock 与 Patch

5.1 unittest.mock

from unittest.mock import Mock, patch

def test_api_call():
    mock_response = Mock()
    mock_response.json.return_value = {"status": "ok"}
    mock_response.status_code = 200

    with patch("requests.get", return_value=mock_response):
        result = fetch_data("https://api.example.com")
        assert result["status"] == "ok"

5.2 pytest-mock

def test_database_query(mocker):
    mock_db = mocker.patch("app.database.query")
    mock_db.return_value = [{"id": 1, "name": "Alice"}]

    result = get_users()
    assert len(result) == 1
    mock_db.assert_called_once()

6. 插件生态

插件功能
pytest-cov覆盖率
pytest-mockMock 封装
pytest-asyncio异步测试
pytest-djangoDjango 集成
pytest-flaskFlask 集成
pytest-xdist并行执行
pytest-timeout超时控制
pytest-randomly随机顺序

7. 配置

7.1 pyproject.toml

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-v --tb=short --strict-markers"
markers = [
    "slow: slow tests",
    "integration: integration tests",
]

7.2 覆盖率

pytest --cov=app --cov-report=html --cov-report=term-missing

8. 最佳实践

实践描述
命名规范test_ 前缀
单一断言每个测试一个关注点
AAA 模式Arrange-Act-Assert
Fixture 复用conftest.py 共享
参数化减少重复代码
Mock 外部依赖隔离测试
覆盖率目标80%+