# -*- coding: utf-8 -*-
"""
============================
pytest 大师课:从入门到精通
============================
版本:2.0 | 全面升级版
"""
# ====================
# 第一部分:pytest 核心概念
# ====================
"""
1. 为什么选择 pytest?
- 更简洁的测试代码(相比 unittest)
- 强大的断言机制(无需记忆特定方法)
- 灵活的夹具系统(fixtures)
- 丰富的插件生态(1000+ 插件)
- 智能测试发现与执行
2. 核心组件:
• 测试发现:自动查找 test_*.py 文件
• 测试执行:并行、选择性和顺序执行
• 报告系统:多种格式输出
• 插件架构:高度可扩展
3. 安装与升级:
pip install -U pytest pytest-html pytest-xdist pytest-cov pytest-mock
"""
# ====================
# 第二部分:基础到进阶示例
# ====================
import os
import pytest
import sys
from datetime import datetime
from unittest.mock import MagicMock, patch
# --------------------
# 1. 测试函数与断言
# --------------------
def test_addition():
"""基础断言示例"""
assert 1 + 1 == 2, "加法计算错误"
def test_container_operations():
"""容器操作断言"""
numbers = [1, 2, 3, 4, 5]
assert 3 in numbers
assert numbers == [1, 2, 3, 4, 5]
assert all(n > 0 for n in numbers)
assert any(n % 2 == 0 for n in numbers)
def test_floating_point():
"""浮点数近似断言"""
result = 0.1 + 0.2
assert result == pytest.approx(0.3, rel=1e-5)
# --------------------
# 2. 夹具(fixture)系统详解
# --------------------
@pytest.fixture(scope="module")
def database_connection():
"""模块级夹具 - 整个测试模块共享"""
print("\n>>> 创建数据库连接 (模块级)")
conn = {"status": "connected", "version": "1.2.3"}
yield conn
print("\n>>> 关闭数据库连接 (模块级)")
@pytest.fixture
def temporary_user(database_connection):
"""函数级夹具 - 依赖其他夹具"""
print("\n>> 创建临时用户")
user = {"id": 1001, "name": "test_user", "created_at": datetime.now()}
yield user
print("\n>> 删除临时用户")
def test_user_creation(temporary_user):
"""测试用户创建"""
assert temporary_user["id"] == 1001
assert "test" in temporary_user["name"]
# --------------------
# 3. 参数化高级用法
# --------------------
def is_prime(n):
"""判断质数的函数"""
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
# 参数化测试 + ID自定义
@pytest.mark.parametrize(
"number, expected",
[
(2, True),
(3, True),
(4, False),
(17, True),
(25, False),
pytest.param(1, False, id="edge_case_1"),
pytest.param(0, False, id="edge_case_0"),
],
ids=lambda x: f"num_{x[0]}"
)
def test_prime_numbers(number, expected):
"""测试质数判断函数"""
assert is_prime(number) == expected
# --------------------
# 4. 标记系统高级应用
# --------------------
@pytest.mark.slow
@pytest.mark.integration
def test_external_api_call():
"""集成测试:调用外部API"""
import time
time.sleep(1.5) # 模拟API调用
assert True
@pytest.mark.skipif(sys.version_info < (3, 8), reason="需要Python 3.8+")
def test_walrus_operator():
"""测试海象运算符"""
data = [1, 2, 3, 4, 5]
if (n := len(data)) > 3:
assert n == 5
@pytest.mark.xfail(sys.platform == "win32", reason="Windows平台已知问题")
def test_filesystem_case_sensitivity():
"""测试文件系统大小写敏感性"""
with open("tempfile.txt", "w") as f:
f.write("test")
assert not os.path.exists("TEMPFILE.txt") # Linux/Mac大小写敏感
# --------------------
# 5. 异常处理与警告
# --------------------
def test_expected_exception():
"""测试预期异常"""
with pytest.raises(ValueError) as exc_info:
int("not_a_number")
assert "invalid literal" in str(exc_info.value)
def test_warnings():
"""测试警告捕获"""
with pytest.warns(DeprecationWarning):
import deprecated_module # 假设的废弃模块
# --------------------
# 6. Mock与猴子补丁
# --------------------
def fetch_weather_data(api_url):
"""获取天气数据的函数"""
# 实际实现会调用外部API
return {"temp": 25, "condition": "sunny"}
def test_mock_external_api():
"""使用Mock替代外部API调用"""
# 创建Mock替代实际函数
with patch(__name__ + ".fetch_weather_data") as mock_weather:
mock_weather.return_value = {"temp": 30, "condition": "cloudy"}
result = fetch_weather_data("https://weather-api.com")
assert result["temp"] == 30
mock_weather.assert_called_once_with("https://weather-api.com")
# --------------------
# 7. 临时文件与目录
# --------------------
def test_create_temp_files(tmp_path):
"""测试临时文件操作"""
# 创建临时目录结构
data_dir = tmp_path / "data"
data_dir.mkdir()
# 创建文件
file_path = data_dir / "test.csv"
file_path.write_text("id,name\n1,Alice\n2,Bob")
# 验证内容
content = file_path.read_text()
assert "Alice" in content
assert "Bob" in content
# ====================
# 第三部分:高级工程实践
# ====================
# --------------------
# 8. 测试覆盖率分析
# --------------------
"""
使用pytest-cov生成覆盖率报告:
1. 基本报告:pytest --cov=my_module
2. HTML报告:pytest --cov=my_module --cov-report=html
3. 阈值控制:pytest --cov=my_module --cov-fail-under=90
"""
# 被测代码
def calculate_discount(price, discount_percent):
"""计算折扣后的价格"""
if discount_percent < 0 or discount_percent > 100:
raise ValueError("折扣率必须在0-100之间")
return price * (1 - discount_percent / 100)
# 测试用例
def test_discount_calculation():
assert calculate_discount(100, 20) == 80
assert calculate_discount(50, 10) == 45
def test_discount_edge_cases():
with pytest.raises(ValueError):
calculate_discount(100, -5)
with pytest.raises(ValueError):
calculate_discount(100, 110)
# --------------------
# 9. 并行测试执行
# --------------------
"""
使用pytest-xdist进行并行测试:
1. 安装:pip install pytest-xdist
2. 运行:pytest -n auto # 自动检测CPU核心数
3. 指定核心数:pytest -n 4
注意:确保测试是独立的,无共享状态
"""
@pytest.mark.parametrize("x", range(100))
def test_parallel_execution(x):
"""模拟大量可并行测试"""
assert x * 0 == 0
# --------------------
# 10. 自定义夹具参数化
# --------------------
def generate_test_data():
"""生成测试数据组合"""
return [
{"input": "admin", "role": "administrator"},
{"input": "user", "role": "standard"},
{"input": "guest", "role": "limited"}
]
@pytest.fixture(params=generate_test_data())
def user_account(request):
"""参数化夹具"""
return request.param
def test_user_roles(user_account):
"""测试不同用户角色"""
assert user_account["role"] in ["administrator", "standard", "limited"]
# --------------------
# 11. 测试配置与钩子
# --------------------
"""
conftest.py 示例:
# 项目根目录下的conftest.py
def pytest_configure(config):
# 全局配置
config.addinivalue_line("markers", "e2e: 端到端测试")
@pytest.fixture(scope="session")
def global_config():
return load_config()
"""
# ====================
# 第四部分:最佳工程实践
# ====================
"""
1. 测试目录结构:
project/
├── src/ # 源代码
├── tests/ # 测试代码
│ ├── unit/ # 单元测试
│ ├── integration/ # 集成测试
│ ├── e2e/ # 端到端测试
│ └── conftest.py # 共享夹具配置
├── pyproject.toml # 项目配置
└── pytest.ini # pytest配置
2. pytest.ini 配置示例:
[pytest]
addopts = -v --color=yes --durations=10
testpaths = tests
markers =
slow: 慢速测试
integration: 集成测试
e2e: 端到端测试
3. 测试金字塔原则:
• 70% 单元测试 (快速、隔离)
• 20% 集成测试 (服务间交互)
• 10% 端到端测试 (完整用户流程)
4. 测试命名规范:
• 测试文件:test_<module>_<feature>.py
• 测试函数:test_<scenario>_<expected_result>()
• 测试类:Test<Feature>
"""
# ====================
# 第五部分:测试报告与CI集成
# ====================
"""
1. 生成HTML报告:
pytest --html=report.html --css=style.css
2. JUnit XML报告(Jenkins兼容):
pytest --junitxml=test-results.xml
3. GitHub Actions 集成示例:
name: Python 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.x'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: pytest --cov=src --cov-report=xml
4. 测试质量门禁:
• 测试覆盖率 > 80%
• 无失败的测试用例
• 关键路径测试通过
• 性能测试达标
"""
# ====================
# 第六部分:完整测试示例
# ====================
class UserManagementSystem:
"""用户管理系统模拟类"""
def __init__(self):
self.users = {}
self.next_id = 1
def add_user(self, name, email):
"""添加用户"""
if not email or "@" not in email:
raise ValueError("无效的邮箱地址")
user_id = self.next_id
self.users[user_id] = {"name": name, "email": email}
self.next_id += 1
return user_id
def get_user(self, user_id):
"""获取用户信息"""
return self.users.get(user_id)
def delete_user(self, user_id):
"""删除用户"""
if user_id not in self.users:
raise KeyError("用户不存在")
del self.users[user_id]
@pytest.fixture
def user_system():
"""用户管理系统夹具"""
system = UserManagementSystem()
# 添加初始用户
system.add_user("Admin", "admin@example.com")
return system
class TestUserManagement:
"""用户管理系统测试套件"""
def test_add_user(self, user_system):
"""测试添加用户"""
user_id = user_system.add_user("Alice", "alice@example.com")
assert user_id == 2
assert user_system.get_user(2)["name"] == "Alice"
def test_add_invalid_email(self, user_system):
"""测试无效邮箱"""
with pytest.raises(ValueError) as excinfo:
user_system.add_user("Bob", "invalid-email")
assert "无效的邮箱地址" in str(excinfo.value)
def test_delete_user(self, user_system):
"""测试删除用户"""
user_system.delete_user(1)
with pytest.raises(KeyError):
user_system.get_user(1)
@pytest.mark.parametrize("email", [
"test@example.com",
"user.name@domain.co",
"unicode@例子.中国"
])
def test_valid_emails(self, user_system, email):
"""参数化测试有效邮箱格式"""
user_id = user_system.add_user("Test", email)
assert user_id > 1
# ====================
# 执行测试
# ====================
if __name__ == "__main__":
# 启动测试并生成HTML报告
import subprocess
result = subprocess.run([
"pytest",
__file__,
"-v",
"--html=test_report.html",
"--cov=",
"--cov-report=html"
])
sys.exit(result.returncode)
ERROR: usage: pytest [options] [file_or_dir] [file_or_dir] [...]
pytest: error: unrecognized arguments: --html=test_report.html --cov= --cov-report=html
inifile: None
rootdir: /Users/10280281/PycharmProjects/PythonProject/.venv/lib/python3.9/python_course/module2_functions
代码报错了,帮我看看哪里的问题,我是在mac上运行的
最新发布