关于 Pytest Testing
面向 Python 开发者的 pytest 测试学习技能,涵盖测试设计、测试驱动开发和 CI 集成。
主要功能
- 01单元、集成和功能测试
- 02Fixture 与测试设置
- 03模拟外部依赖
- 04测试驱动开发
- 05覆盖率与 CI 集成
适用场景
- 01为 Python 项目构建 pytest 测试套件
- 02使用模拟请求测试 API
- 03添加覆盖率报告和 CI 检查
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ pluginagentmarketplace/ custom-plugin-python --skill pytest-testing安装后在 Claude Code 中输入/pytest-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/config.yamlreferences/GUIDE.mdscripts/helper.py详细介绍
Pytest 测试
概述
使用 Python 最受欢迎的测试框架 pytest,掌握软件测试。学习测试驱动开发(TDD)、编写易于维护的测试,并通过全面的测试策略确保代码质量。
学习目标
- 使用 pytest 编写单元、集成和功能测试
- 使用 fixture 进行测试准备和清理
- 有效模拟外部依赖
- 实施测试驱动开发(TDD)
- 衡量并提高代码覆盖率
- 将测试集成到 CI/CD 流水线中
核心主题
1. Pytest 基础
- 测试发现和命名约定
- 断言和比较
- 测试组织(文件、类、模块)
- 运行测试(命令行选项)
- 标记和测试选择
- 参数化测试
代码示例:
# test_calculator.py
import pytest
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# Basic test
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
# Test exceptions
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
# Parametrized test
@pytest.mark.parametrize("a,b,expected", [
(10, 2, 5),
(20, 4, 5),
(100, 10, 10),
(-10, 2, -5),
])
def test_divide(a, b, expected):
assert divide(a, b) == expected
# Test with marker
@pytest.mark.slow
def test_complex_operation():
# This test takes a long time
result = sum(range(1000000))
assert result == 499999500000
2. Fixture 与测试准备
- Fixture 作用域(函数、类、模块、会话)
- Fixture 依赖
- 参数化 Fixture
- 内置 Fixture(tmpdir、capsys、monkeypatch)
- 用于共享 Fixture 的 conftest.py
代码示例:
# conftest.py
import pytest
import tempfile
from pathlib import Path
@pytest.fixture
def sample_data():
"""Provide sample data for tests"""
return {
'users': [
{'id': 1, 'name': 'Alice', 'email': '[email protected]'},
{'id': 2, 'name': 'Bob', 'email': '[email protected]'},
]
}
@pytest.fixture
def temp_file():
"""Create temporary file for testing"""
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
f.write("Test data")
temp_path = f.name
yield temp_path
# Cleanup
Path(temp_path).unlink()
@pytest.fixture(scope='module')
def database_connection():
"""Module-scoped database connection"""
db = DatabaseConnection('test.db')
db.connect()
yield db
db.close()
# test_users.py
def test_user_count(sample_data):
assert len(sample_data['users']) == 2
def test_user_names(sample_data):
names = [user['name'] for user in sample_data['users']]
assert 'Alice' in names
assert 'Bob' in names
def test_file_operations(temp_file):
content = Path(temp_file).read_text()
assert content == "Test data"
3. 模拟与测试替身
- unittest.mock 基础
- 模拟函数和方法
- 修补对象
- Mock 断言
- 副作用和返回值
- 使用外部依赖进行测试
代码示例:
# api_client.py
import requests
class APIClient:
def __init__(self, base_url):
self.base_url = base_url
def get_user(self, user_id):
response = requests.get(f"{self.base_url}/users/{user_id}")
response.raise_for_status()
return response.json()
def create_user(self, user_data):
response = requests.post(f"{self.base_url}/users", json=user_data)
response.raise_for_status()
return response.json()
# test_api_client.py
from unittest.mock import Mock, patch
import pytest
@patch('api_client.requests.get')
def test_get_user(mock_get):
# Setup mock
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'Alice'}
mock_response.raise_for_status.return_value = None
mock_get.return_value = mock_response
# Test
client = APIClient('https://api.example.com')
user = client.get_user(1)
# Assertions
assert user['name'] == 'Alice'
mock_get.assert_called_once_with('https://api.example.com/users/1')
@patch('api_client.requests.post')
def test_create_user(mock_post):
# Setup mock
mock_response = Mock()
mock_response.json.return_value = {'id': 3, 'name': 'Charlie'}
mock_post.return_value = mock_response
# Test
client = APIClient('https://api.example.com')
user_data = {'name': 'Charlie', 'email': '[email protected]'}
result = client.create_user(user_data)
# Assertions
assert result['id'] == 3
mock_post.assert_called_once_with(
'https://api.example.com/users',
json=user_data
)
4. 覆盖率与 CI/CD 集成
- 使用 pytest-cov 衡量代码覆盖率
- 覆盖率报告(终端、HTML、XML)
- 设置覆盖率阈值
- 集成 GitHub Actions
- 集成 GitLab CI
- pre-commit 钩子
代码示例:
# pytest.ini
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
--cov=myapp
--cov-report=html
--cov-report=term-missing
--cov-fail-under=80
-v
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest --cov=myapp --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
# Command line usage
# Run all tests
pytest
# Run with coverage
pytest --cov=myapp
# Generate HTML coverage report
pytest --cov=myapp --cov-report=html
# Run specific test file
pytest tests/test_api.py
# Run tests with marker
pytest -m slow
# Run tests with verbose output
pytest -v
# Stop on first failure
pytest -x
动手实践
项目 1:计算器 TDD
使用测试驱动开发构建计算器。
要求:
- 在实现之前编写测试
- 基本运算(加、减、乘、除)
- 错误处理(除以零)
- 科学运算(幂、平方根、对数)
- 测试覆盖率 > 90%
关键技能: TDD 工作流、参数化测试、异常测试
项目 2:API 测试套件
为 REST API 创建全面的测试套件。
要求:
- 模拟 HTTP 请求
- 测试 CRUD 操作
- 错误处理测试
- 身份验证测试
- 集成测试
- 设置 CI/CD 流水线
关键技能: 模拟、Fixture、集成测试
项目 3:数据库测试
使用 Fixture 和事务测试数据库操作。
要求:
- 设置测试数据库 Fixture
- 测试 CRUD 操作
- 事务回滚
- 数据验证
- 性能测试
- 覆盖率报告
关键技能: 数据库 Fixture、清理、性能测试
评估标准
- 编写清晰、易于维护的测试
- 合理使用 Fixture
- 有效模拟外部依赖
- 实现 >80% 的代码覆盖率
- 遵循 TDD 原则
- 将测试集成到 CI/CD 中
- 编写有意义的断言
资源
官方文档
- Pytest 文档 - 官方文档
- pytest-cov - 覆盖率插件
- unittest.mock - 模拟库
学习平台
- 使用 Python 进行测试驱动开发 - TDD 书籍
- 使用 pytest 进行 Python 测试 - Brian Okken 著作
- Real Python 测试 - 教程
工具
- pytest-xdist - 并行测试
- pytest-mock - 模拟辅助工具
- Hypothesis - 基于属性的测试
- tox - 测试自动化
后续步骤
掌握 pytest 后,继续探索:
- 基于属性的测试 - Hypothesis 库
- 性能测试 - pytest-benchmark
- 变异测试 - mutmut
- 负载测试 - Locust、pytest-load
评论
支持 Markdown