pytest — 测试框架
pytest 是 Python 最流行的测试框架,简洁的语法和强大的插件生态让测试变得愉快。
安装
bash
pip install pytest pytest-cov pytest-asyncio基础用法
python
# tests/test_math.py
def add(a, b):
return a + b
# pytest 自动发现 test_ 开头的函数
def test_add_integers():
assert add(1, 2) == 3
def test_add_floats():
assert add(1.1, 2.2) == pytest.approx(3.3) # 浮点数比较
def test_add_strings():
assert add("hello", " world") == "hello world"bash
pytest # 运行所有测试
pytest tests/test_math.py # 运行指定文件
pytest -v # 详细输出
pytest -k "add" # 只运行名称含 "add" 的测试
pytest --tb=short # 简短错误信息Fixture — 测试夹具
python
import pytest
# 函数级 fixture(每个测试函数运行一次)
@pytest.fixture
def sample_user():
return {"id": 1, "name": "Alice", "email": "alice@example.com"}
# 模块级 fixture(整个模块只运行一次)
@pytest.fixture(scope="module")
def db_connection():
conn = create_test_db()
yield conn # yield 前是 setup,yield 后是 teardown
conn.close()
# 会话级 fixture(整个测试会话只运行一次)
@pytest.fixture(scope="session")
def app_config():
return {"debug": True, "db_url": "sqlite:///:memory:"}
def test_user_name(sample_user):
assert sample_user["name"] == "Alice"
def test_user_email(sample_user):
assert "@" in sample_user["email"]参数化测试
python
import pytest
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add(a, b, expected):
assert add(a, b) == expected
# 参数化 fixture
@pytest.fixture(params=["sqlite", "postgresql"])
def database(request):
db = create_db(request.param)
yield db
db.cleanup()
def test_insert(database):
# 对每种数据库都运行
database.insert({"key": "value"})
assert database.get("key") == "value"异常测试
python
import pytest
def divide(a, b):
if b == 0:
raise ZeroDivisionError("除数不能为零")
return a / b
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError, match="除数不能为零"):
divide(10, 0)
def test_divide_by_zero_type():
with pytest.raises(ZeroDivisionError) as exc_info:
divide(10, 0)
assert "除数不能为零" in str(exc_info.value)Mock — 模拟外部依赖
python
from unittest.mock import Mock, patch, AsyncMock
import pytest
# patch 装饰器
@patch("mymodule.requests.get")
def test_fetch_user(mock_get):
mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
mock_get.return_value.status_code = 200
result = fetch_user(1)
assert result["name"] == "Alice"
mock_get.assert_called_once_with("https://api.example.com/users/1")
# patch 上下文管理器
def test_send_email():
with patch("mymodule.smtplib.SMTP") as mock_smtp:
send_welcome_email("user@example.com")
mock_smtp.return_value.__enter__.return_value.sendmail.assert_called_once()
# pytest-mock 插件(更简洁)
def test_with_mocker(mocker):
mock_db = mocker.patch("mymodule.get_db")
mock_db.return_value.query.return_value = [{"id": 1}]
result = get_users()
assert len(result) == 1异步测试
python
import pytest
import asyncio
# pytest.ini 或 pyproject.toml 中配置:
# [tool.pytest.ini_options]
# asyncio_mode = "auto"
@pytest.mark.asyncio
async def test_async_function():
result = await some_async_function()
assert result == "expected"
# 异步 fixture
@pytest.fixture
async def async_client():
async with httpx.AsyncClient(app=app, base_url="http://test") as client:
yield client
@pytest.mark.asyncio
async def test_api_endpoint(async_client):
response = await async_client.get("/users/1")
assert response.status_code == 200FastAPI 测试
python
from fastapi.testclient import TestClient
import pytest
from app.main import app
@pytest.fixture
def client():
with TestClient(app) as c:
yield c
def test_read_root(client):
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello, FastAPI!"}
def test_create_user(client):
payload = {"name": "Alice", "email": "alice@example.com", "age": 30}
response = client.post("/users/", json=payload)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Alice"
assert "id" in data覆盖率报告
bash
pytest --cov=src --cov-report=html --cov-report=term-missing
# 生成 HTML 报告
open htmlcov/index.htmltoml
# pyproject.toml
[tool.coverage.run]
source = ["src"]
omit = ["*/tests/*", "*/migrations/*"]
[tool.coverage.report]
fail_under = 80 # 覆盖率低于 80% 则失败测试最佳实践
- 测试文件放在
tests/目录,镜像src/结构 - 每个测试只测一件事
- 测试名称描述行为:
test_create_user_returns_201 - 用 fixture 管理测试数据,避免重复
- Mock 外部依赖(HTTP、数据库、文件系统)