LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

30天学会Python编程:30.Pytest 实践教程

admin
2025年7月17日 21:58 本文热度 9

1. Pytest 基础入门

1.1 为什么选择 Pytest

Pytest 是 Python 最流行的测试框架之一,相比标准库的 unittest 具有以下优势:

  • 简洁的语法:无需继承测试类,使用普通函数和 assert 语句
  • 强大的功能:丰富的插件系统支持各种测试场景
  • 自动发现:智能识别测试文件和测试函数
  • 详细报告:清晰的测试失败信息
  • 社区支持:活跃的社区和丰富的文档资源
# 安装 pytest
pip install pytest

1.2 第一个测试示例

# content of test_sample.py
def add(a, b):
    return a + b

def test_add():
    assert add(23) == 5

运行测试:

pytest test_sample.py

1.3 测试发现规则

Pytest 自动发现测试遵循以下规则:

  • 文件名匹配 test_*.py 或 *_test.py
  • 函数名以 test_ 开头
  • 类名以 Test 开头(无 __init__ 方法)
  • 类中的方法名以 test_ 开头

2. Pytest 主要功能

2.1 断言机制

Pytest 使用 Python 原生 assert 语句,并提供丰富的断言重写功能:

def test_string_operations():
    s = "pytest"
    assert s.upper() == "PYTEST"
    assert "test" in s
    assert s.startswith("py")
    assert len(s) == 6

def test_list_operations():
    numbers = [123]
    assert 1 in numbers
    assert numbers == [123]
    assert numbers[0] < numbers[-1]

2.2 异常测试

测试代码是否抛出预期异常:

import pytest

def divide(a, b):
    if b == 0:
        raise ValueError("除数不能为零")
    return a / b

def test_divide():
    assert divide(102) == 5
    
    with pytest.raises(ValueError) as excinfo:
        divide(100)
    assert "除数不能为零" in str(excinfo.value)

2.3 测试固件 (Fixtures)

Fixtures 是 Pytest 最强大的功能之一,用于测试资源的设置和清理:

import pytest

@pytest.fixture
def database():
    print("\n创建测试数据库连接")
    db = connect_to_test_db()
    yield db  # 测试执行时暂停,执行完成后继续
    print("\n关闭数据库连接")
    db.close()

def test_query(database):
    result = database.query("SELECT * FROM users")
    assert len(result) > 0

def test_insert(database):
    database.insert("users", {"name""Alice"})
    result = database.query("SELECT * FROM users WHERE name='Alice'")
    assert len(result) == 1

3. Fixtures 用法

3.1 Fixture 作用域

作用域
描述
适用场景
function
每个测试函数执行一次(默认)
轻量级资源
class
每个测试类执行一次
类共享资源
module
每个测试模块执行一次
模块级共享
package
每个测试包执行一次
包级共享
session
整个测试会话执行一次
全局资源
@pytest.fixture(scope="module")
def shared_resource():
    print("\n初始化共享资源")
    resource = create_expensive_resource()
    yield resource
    print("\n清理共享资源")
    resource.cleanup()

3.2 Fixture 参数化

import pytest

@pytest.fixture(params=["utf-8""utf-16""ascii"])
def encoding(request):
    return request.param

def test_encoding(encoding):
    text = "Hello World"
    encoded = text.encode(encoding)
    decoded = encoded.decode(encoding)
    assert decoded == text

3.3 内置 Fixtures

Pytest 提供许多有用的内置 fixtures:

def test_temp_dir(tmp_path):
    """使用临时目录 fixture"""
    file = tmp_path / "test.txt"
    file.write_text("Hello pytest")
    assert file.read_text() == "Hello pytest"

def test_capsys(capsys):
    """捕获标准输出"""
    print("Hello pytest")
    captured = capsys.readouterr()
    assert captured.out == "Hello pytest\n"

4. 参数化测试

4.1 基本参数化

import pytest

@pytest.mark.parametrize("a,b,expected", [
    (123),
    (5, -50),
    (100200300),
]
)

def test_add(a, b, expected):
    assert add(a, b) == expected

4.2 参数化组合

@pytest.mark.parametrize("x", [01])
@pytest.mark.parametrize("y", [23])
def test_combinations(x, y):
    assert x + y == y + x

4.3 自定义参数 ID

@pytest.mark.parametrize("input,expected", [
    ("3+5"8),
    ("2+4"6),
    ("6*9"42, marks=pytest.mark.xfail),
], ids=["3+5=8""2+4=6""6*9=42 (xfail)"]
)

def test_eval(input, expected):
    assert eval(input) == expected

5. 标记和过滤测试

5.1 内置标记

@pytest.mark.skip(reason="功能尚未实现")
def test_unimplemented():
    assert False

@pytest.mark.skipif(sys.version_info < (38),
                   reason="需要 Python 3.8+"
)

def test_python38_feature():
    assert True

@pytest.mark.xfail
def test_experimental():
    assert False  # 预期失败

5.2 自定义标记

# conftest.py
def pytest_configure(config):
    config.addinivalue_line(
        "markers""slow: 标记为慢速测试"
    )

@pytest.mark.slow
def test_large_data_processing():
    time.sleep(10)
    assert True

运行指定标记的测试:

pytest -m slow  # 只运行慢测试
pytest -m "not slow"  # 排除慢测试

6. 插件系统

6.1 常用插件

插件名称
功能
安装命令
pytest-cov
测试覆盖率
pip install pytest-cov
pytest-xdist
并行测试
pip install pytest-xdist
pytest-mock
Mock 集成
pip install pytest-mock
pytest-html
HTML 报告
pip install pytest-html
pytest-asyncio
异步测试
pip install pytest-asyncio

6.2 插件使用示例

# 生成覆盖率报告
pytest --cov=my_project --cov-report=html

# 并行运行测试
pytest -n auto

# 生成 HTML 报告
pytest --html=report.html

7. 测试实践

7.1 测试目录结构

project/
├── src/
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
└── tests/
    ├── unit/
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── test_module1.py
    │   └── test_module2.py
    ├── integration/
    │   └── test_integration.py
    └── functional/
        └── test_functional.py

7.2 测试命名规范

  • 测试文件:test_<模块名>.py 或 <模块名>_test.py
  • 测试函数:test_<功能>_<条件>_<预期>
  • 测试类:Test<功能>

7.3 测试设计原则

  1. 单一职责:每个测试只验证一个行为
  2. 独立性:测试之间不依赖执行顺序
  3. 可读性:测试名称清晰表达意图
  4. 确定性:相同输入总是相同结果
  5. 快速执行:避免慢速测试阻碍开发

8. 高级主题

8.1 Mock 和 Monkeypatch

import pytest

def test_mocking(mocker):
    # 使用 pytest-mock 插件
    mock_requests = mocker.patch("requests.get")
    mock_requests.return_value.status_code = 200
    
    response = requests.get("http://example.com")
    assert response.status_code == 200
    mock_requests.assert_called_once_with("http://example.com")

def test_monkeypatch(monkeypatch):
    # 临时修改环境变量
    monkeypatch.setenv("DEBUG""True")
    assert os.getenv("DEBUG") == "True"
    
    # 修改系统函数
    monkeypatch.setattr(time, "sleep"lambda x: None)
    time.sleep(10)  # 实际上不会等待

8.2 异步测试

import pytest
import asyncio

@pytest.mark.asyncio
async def test_async_code():
    result = await async_function()
    assert result == "expected"

8.3 自定义钩子

# conftest.py
def pytest_runtest_logreport(report):
    if report.when == "call" and report.failed:
        print(f"\n测试失败: {report.nodeid}")
        print(f"错误信息: {report.longreprtext}")

9. 应用举例

9.1 Web 应用测试

from fastapi.testclient import TestClient
from myapp.main import app

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

def test_homepage(client):
    response = client.get("/")
    assert response.status_code == 200
    assert "Welcome" in response.text

def test_login(client):
    response = client.post("/login", json={
        "username""admin",
        "password""secret"
    })
    assert response.status_code == 200
    assert "token" in response.json()

9.2 数据库测试

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

@pytest.fixture(scope="module")
def db_session():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()
    yield session
    session.close()

def test_create_user(db_session):
    user = User(name="Alice", email="alice@example.com")
    db_session.add(user)
    db_session.commit()
    
    saved = db_session.query(User).filter_by(email="alice@example.com").first()
    assert saved is not None
    assert saved.name == "Alice"

10. 常见问题

10.1 测试失败排查

  1. 查看详细输出:pytest -v
  2. 打印调试信息:pytest --pdb 进入调试器
  3. 查看完整回溯:pytest --full-trace
  4. 只运行失败测试:pytest --lf

10.2 性能优化

# 找出最慢的测试
pytest --durations=10

# 并行运行测试
pytest -n auto

# 禁用插件
pytest -p no:cov -p no:xdist

10.3 配置选项

# pytest.ini 配置文件
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --color=yes
markers =
    slow: 标记为慢速测试
    integration: 集成测试

11. 学习资源

11.1 官方文档

  • Pytest 官方文档:(https://docs.pytest.org/)
  • Pytest 插件列表:(https://docs.pytest.org/en/latest/reference/plugin_list.html)
  • Pytest 最佳实践:(https://docs.pytest.org/en/latest/goodpractices.html)

11.2 进阶路线

通过本教程,我们可以掌握 Pytest 从基础到高级的各种技巧。Pytest 的强大功能和灵活性使其成为 Python 测试的首选工具。持续实践并探索其生态系统,将显著提升您的测试效率和代码质量。


阅读原文:原文链接


该文章在 2025/7/18 10:44:48 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved