跳到主要内容

测试

问题

Python 项目如何做测试?pytest 的核心功能有哪些?

答案

pytest 基础

tests/test_user.py
import pytest
from myapp.user import create_user, UserExistsError

# 基础测试
def test_create_user():
user = create_user("Alice", "alice@example.com")
assert user.name == "Alice"
assert user.email == "alice@example.com"

# 异常测试
def test_duplicate_user():
create_user("Bob", "bob@example.com")
with pytest.raises(UserExistsError, match="already exists"):
create_user("Bob", "bob@example.com")

# 参数化
@pytest.mark.parametrize("email,valid", [
("user@example.com", True),
("invalid", False),
("@example.com", False),
("user@.com", False),
])
def test_validate_email(email: str, valid: bool):
assert validate_email(email) == valid

Fixture

import pytest
from sqlalchemy.ext.asyncio import AsyncSession

@pytest.fixture
async def db_session() -> AsyncGenerator[AsyncSession, None]:
"""每个测试使用独立事务,测试后回滚"""
async with async_session() as session:
async with session.begin():
yield session
await session.rollback()

@pytest.fixture
def user(db_session: AsyncSession) -> User:
"""依赖 db_session fixture"""
user = User(name="Test", email="test@example.com")
db_session.add(user)
return user

async def test_get_user(db_session: AsyncSession, user: User):
result = await get_user(db_session, user.id)
assert result.name == "Test"

Mock

from unittest.mock import AsyncMock, patch, MagicMock

# Mock 外部 API
@patch("myapp.service.httpx.AsyncClient.get")
async def test_fetch_weather(mock_get: AsyncMock):
mock_get.return_value = MagicMock(
status_code=200,
json=lambda: {"temp": 25, "city": "Beijing"},
)

result = await fetch_weather("Beijing")
assert result["temp"] == 25
mock_get.assert_called_once()

# Mock 数据库
@patch("myapp.repo.get_user_by_email")
async def test_login(mock_get_user: AsyncMock):
mock_get_user.return_value = User(name="Alice", password_hash="...")
result = await login("alice@example.com", "password")
assert result.token is not None

覆盖率

# 运行测试 + 覆盖率
pytest --cov=myapp --cov-report=html tests/

# pyproject.toml 配置
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

[tool.coverage.run]
source = ["myapp"]
omit = ["*/tests/*", "*/migrations/*"]

常见面试问题

Q1: pytest 和 unittest 的区别?

答案

特性pytestunittest
断言原生 assertself.assertEqual()
Fixture函数式,可组合setUp/tearDown
参数化@pytest.mark.parametrizesubTest
插件极丰富(1000+)
异步pytest-asyncioIsolatedAsyncioTestCase

Q2: 测试金字塔在 Python 项目中怎么落地?

答案

         /  E2E  \        ←  少量:Playwright / Selenium
/ 集成测试 \ ← 中量:测试 API + DB
/ 单元测试 \ ← 大量:纯逻辑测试,Mock 依赖
  • 单元测试(70%):测试纯函数、业务逻辑
  • 集成测试(20%):测试 API 端到端(使用测试数据库)
  • E2E 测试(10%):核心流程的端到端

Q3: 如何 Mock 异步函数?

答案

from unittest.mock import AsyncMock

mock_fn = AsyncMock(return_value={"data": "test"})
result = await mock_fn()
assert result == {"data": "test"}

相关链接