MBE 测试指南

本文档介绍 MBE (Mises Behavior Engine) 的测试框架、测试编写和运行方法。

目录


测试框架

MBE 使用以下测试工具:

  • pytest: 测试框架
  • pytest-asyncio: 异步测试支持
  • pytest-cov: 代码覆盖率
  • pytest-mock: Mock 工具
  • httpx: HTTP 测试客户端
  • faker: 生成测试数据

安装测试依赖

pip install -r tests/requirements.txt

测试分类

1. 单元测试(Unit Tests)

测试单个函数或类,不依赖外部服务。

标记: @pytest.mark.unit

位置: tests/unit/

示例:

@pytest.mark.unit
def test_sanitize_input():
    result = sanitize_input("<script>alert('xss')</script>")
    assert "<script>" not in result

2. 集成测试(Integration Tests)

测试多个组件的交互,可能依赖数据库、Redis 等。

标记: @pytest.mark.integration

位置: tests/integration/

示例:

@pytest.mark.integration
@pytest.mark.db
async def test_database_connection(db_session):
    result = await db_session.execute(text("SELECT 1"))
    assert result.scalar() == 1

3. E2E 测试(End-to-End Tests)

测试完整的用户流程。

标记: @pytest.mark.e2e

位置: tests/e2e/(待实现)


运行测试

运行所有测试

pytest

运行特定测试类型

# 只运行单元测试
pytest -m unit

# 只运行集成测试
pytest -m integration

# 只运行需要数据库的测试
pytest -m db

# 只运行需要 Redis 的测试
pytest -m redis

运行特定测试文件

pytest tests/unit/test_utils.py
pytest tests/integration/test_api.py

运行特定测试函数

pytest tests/unit/test_utils.py::TestSecurity::test_validate_email

详细输出

pytest -v         # 详细模式
pytest -vv        # 更详细
pytest -s         # 显示 print 输出

并行运行(加速)

pip install pytest-xdist
pytest -n auto    # 自动使用所有 CPU 核心
pytest -n 4       # 使用 4 个进程

编写测试

测试文件命名

  • 文件名: test_*.py*_test.py
  • 类名: Test*
  • 函数名: test_*

使用 Fixtures

Fixtures 在 tests/conftest.py 中定义。

1. HTTP 测试客户端

@pytest.mark.integration
async def test_api_endpoint(client: AsyncClient):
    response = await client.get("/api/health")
    assert response.status_code == 200

2. 数据库会话

@pytest.mark.integration
@pytest.mark.db
async def test_database_query(db_session):
    result = await db_session.execute(text("SELECT 1"))
    assert result.scalar() == 1

3. Redis 客户端

@pytest.mark.integration
@pytest.mark.redis
async def test_redis_operation(redis_client):
    await redis_client.set("key", "value")
    value = await redis_client.get("key")
    assert value == "value"

4. 模拟配置

def test_with_custom_config(mock_settings):
    mock_settings.enable_rate_limit = False
    # 测试代码...

参数化测试

@pytest.mark.parametrize("email,expected", [
    ("test@example.com", True),
    ("invalid.email", False),
    ("", False),
])
def test_validate_email(email, expected):
    assert validate_email(email) == expected

异步测试

@pytest.mark.asyncio
async def test_async_function():
    result = await some_async_function()
    assert result == expected

Mock 和 Patch

@pytest.mark.unit
def test_with_mock(mocker):
    mock_func = mocker.patch("module.function")
    mock_func.return_value = "mocked"
    
    result = call_function_that_uses_mock()
    assert result == "mocked"
    mock_func.assert_called_once()

测试覆盖率

查看覆盖率报告

# 运行测试并生成覆盖率
pytest --cov

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

# 打开报告
open htmlcov/index.html  # macOS
start htmlcov/index.html # Windows
xdg-open htmlcov/index.html # Linux

覆盖率配置

pyproject.toml 中配置:

[tool.pytest.ini_options]
addopts = [
    "--cov=shared/src",
    "--cov=private/core/src",
    "--cov=private/platform/src",
    "--cov-report=html",
    "--cov-report=term-missing",
    "--cov-fail-under=70",  # 最低 70% 覆盖率
]

排除文件

创建 .coveragerc:

[run]
omit =
    */tests/*
    */migrations/*
    */__pycache__/*
    */venv/*

CI 集成

测试自动在 GitHub Actions 中运行。

CI 工作流

.github/workflows/ci.yml 包含:

  1. Validate - 验证 Monorepo 结构
  2. Lint - 代码风格检查
  3. Test Monorepo - 运行所有测试
    • 单元测试
    • 集成测试(带 PostgreSQL + Redis)
    • 代码覆盖率上传到 Codecov
  4. Test Core - 测试私有核心模块
  5. Test Platform - 测试私有平台模块
  6. Test SDK - 测试公开 SDK
  7. Docker Build - 构建 Docker 镜像

本地模拟 CI

# 1. 验证
python tools/dev-workspace/check_boundaries.py --public-only
python tools/dev-workspace/validate_monorepo.py

# 2. Linting
ruff check shared/src/ private/core/src/ private/platform/src/

# 3. 测试(需要 Docker)
docker-compose -f docker-compose.dev.yml up -d postgres redis
pytest -m unit
pytest -m integration
docker-compose -f docker-compose.dev.yml down

测试最佳实践

1. 测试命名

# 好的命名
def test_user_login_with_valid_credentials():
    ...

def test_rate_limit_blocks_excessive_requests():
    ...

# 不好的命名
def test1():
    ...

def test_user():
    ...

2. AAA 模式

def test_function():
    # Arrange(准备)
    user = User(email="test@example.com")
    
    # Act(执行)
    result = user.validate_email()
    
    # Assert(断言)
    assert result is True

3. 一个测试一个断言(理想情况)

# 好
def test_user_email_is_validated():
    assert validate_email("test@example.com") is True

def test_user_email_rejects_invalid():
    assert validate_email("invalid") is False

# 避免
def test_user_email():
    assert validate_email("test@example.com") is True
    assert validate_email("invalid") is False
    assert validate_email("") is False
    # ... 太多断言

4. 使用 Fixture 而非全局变量

# 好
@pytest.fixture
def user():
    return User(email="test@example.com")

def test_user_name(user):
    assert user.email == "test@example.com"

# 避免
USER = User(email="test@example.com")

def test_user_name():
    assert USER.email == "test@example.com"

5. 清理测试数据

@pytest.fixture
async def redis_client():
    redis = await get_redis()
    await redis.select(15)  # 测试专用 DB
    
    yield redis
    
    # 清理
    await redis.flushdb()

调试测试

使用 pdb

def test_something():
    import pdb; pdb.set_trace()
    result = function_to_test()
    assert result == expected

使用 pytest --pdb

pytest --pdb  # 失败时自动进入 pdb
pytest -x --pdb  # 第一个失败时停止并进入 pdb

打印输出

pytest -s  # 显示 print() 输出

只运行失败的测试

pytest --lf  # 只运行上次失败的测试
pytest --ff  # 先运行失败的,再运行其他

持续改进

提高测试覆盖率

# 查看未覆盖的行
pytest --cov --cov-report=term-missing

# 生成 HTML 报告查看详情
pytest --cov --cov-report=html

添加新测试

  1. 确定测试类型(单元/集成)
  2. 在对应目录创建测试文件
  3. 添加适当的标记(@pytest.mark.unit
  4. 使用 Fixtures 简化测试
  5. 运行测试确保通过
  6. 提交代码

常见问题

Q: 测试失败但本地运行正常?

A: 检查环境变量和数据库连接。CI 使用测试数据库。

Q: 如何跳过某些测试?

A: 使用 @pytest.mark.skip:

@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
    ...

Q: 如何标记慢测试?

A: 使用 @pytest.mark.slow:

@pytest.mark.slow
def test_expensive_operation():
    ...

# 跳过慢测试
pytest -m "not slow"

Q: 如何测试私有函数?

A: 通过公共 API 间接测试,或者重构代码使其可测试。


参考资源


总结

MBE 的测试框架提供:

✅ 单元测试 - 快速验证函数逻辑
✅ 集成测试 - 验证组件交互
✅ 代码覆盖率 - 确保测试充分
✅ CI 集成 - 自动运行测试
✅ Fixtures - 简化测试编写
✅ 异步支持 - 测试异步代码

目标:保持 70%+ 代码覆盖率,确保代码质量!