Files
agent-aide/aide-program/docs/07-测试规范.md

709 lines
15 KiB
Markdown
Raw Normal View History

2025-12-13 04:37:41 +08:00
# 测试规范
## 一、测试概述
### 1.1 测试目标
确保 aide 程序的所有功能正确、稳定、可靠,满足设计要求。
### 1.2 测试范围
- 单元测试:核心模块和函数
- 集成测试:命令完整流程
- 跨平台测试Linux、macOS、Windows
- 性能测试:执行时间和资源占用
### 1.3 测试工具
- **pytest**:测试框架
- **pytest-cov**:代码覆盖率
- **pytest-mock**:模拟和打桩
- **pytest-timeout**:超时控制
---
## 二、测试结构
### 2.1 目录结构
```
tests/
├── __init__.py
├── conftest.py # pytest 配置和 fixtures
├── unit/ # 单元测试
│ ├── __init__.py
│ ├── test_output.py # 输出模块测试
│ ├── test_config.py # 配置模块测试
│ └── test_validators.py # 验证函数测试
├── integration/ # 集成测试
│ ├── __init__.py
│ ├── test_init.py # aide init 测试
│ ├── test_env.py # aide env 测试
│ └── test_config_cmd.py # aide config 测试
└── fixtures/ # 测试数据
├── sample_config.toml
└── sample_gitignore
```
### 2.2 命名规范
- 测试文件:`test_<module>.py`
- 测试类:`Test<Feature>`
- 测试函数:`test_<scenario>`
**示例**
```python
# test_config.py
class TestConfig:
def test_load_success(self):
"""测试成功加载配置"""
pass
def test_load_file_not_found(self):
"""测试配置文件不存在"""
pass
```
---
## 三、单元测试
### 3.1 core/output.py 测试
```python
# tests/unit/test_output.py
import pytest
from core.output import ok, warn, err, info
def test_ok_simple(capsys):
"""测试简单成功输出"""
ok("操作成功")
captured = capsys.readouterr()
assert "✓ 操作成功" in captured.out
def test_ok_with_details(capsys):
"""测试带详细信息的成功输出"""
ok("操作成功", ["详细信息1", "详细信息2"])
captured = capsys.readouterr()
assert "✓ 操作成功" in captured.out
assert " 详细信息1" in captured.out
assert " 详细信息2" in captured.out
def test_warn(capsys):
"""测试警告输出"""
warn("这是警告")
captured = capsys.readouterr()
assert "⚠ 这是警告" in captured.out
def test_err(capsys):
"""测试错误输出"""
err("这是错误", ["建议: 检查配置"])
captured = capsys.readouterr()
assert "✗ 这是错误" in captured.out
assert " 建议: 检查配置" in captured.out
def test_info(capsys):
"""测试信息输出"""
info("正在处理...")
captured = capsys.readouterr()
assert "→ 正在处理..." in captured.out
```
### 3.2 core/config.py 测试
```python
# tests/unit/test_config.py
import pytest
from pathlib import Path
from core.config import Config, get_config_value, set_config_value
def test_get_config_value():
"""测试获取配置值"""
config = {
"task": {
"source": "task-now.md"
}
}
assert get_config_value(config, "task.source") == "task-now.md"
assert get_config_value(config, "task.spec", "default") == "default"
def test_set_config_value():
"""测试设置配置值"""
config = {"task": {}}
set_config_value(config, "task.source", "new-task.md")
assert config["task"]["source"] == "new-task.md"
def test_config_load(tmp_path):
"""测试加载配置"""
# 创建测试配置文件
config_dir = tmp_path / ".aide"
config_dir.mkdir()
config_path = config_dir / "config.toml"
config_path.write_text('[task]\nsource = "test.md"\n')
# 加载配置
config = Config(tmp_path)
config.load()
assert config.get("task.source") == "test.md"
def test_config_load_not_found(tmp_path):
"""测试配置文件不存在"""
config = Config(tmp_path)
with pytest.raises(FileNotFoundError):
config.load()
def test_config_save(tmp_path):
"""测试保存配置"""
# 创建配置
config_dir = tmp_path / ".aide"
config_dir.mkdir()
config = Config(tmp_path)
config._config = {"task": {"source": "test.md"}}
config.save()
# 验证文件已创建
config_path = tmp_path / ".aide" / "config.toml"
assert config_path.exists()
# 验证内容
content = config_path.read_text()
assert "task" in content
assert "test.md" in content
```
### 3.3 utils/validators.py 测试
```python
# tests/unit/test_validators.py
import pytest
from utils.validators import is_valid_version_spec, validate_config
def test_is_valid_version_spec():
"""测试版本规格验证"""
assert is_valid_version_spec(">=3.10") == True
assert is_valid_version_spec("3.12") == True
assert is_valid_version_spec(">=3.10,<4.0") == True
assert is_valid_version_spec("invalid") == False
def test_validate_config():
"""测试配置验证"""
# 有效配置
valid_config = {
"task": {"source": "task.md", "spec": "spec.md"},
"env": {"python": {"version": ">=3.10"}},
"output": {"language": "zh-CN"}
}
errors = validate_config(valid_config)
assert len(errors) == 0
# 缺少必需项
invalid_config = {"task": {}}
errors = validate_config(invalid_config)
assert len(errors) > 0
assert any("task.source" in e for e in errors)
```
---
## 四、集成测试
### 4.1 aide init 测试
```python
# tests/integration/test_init.py
import pytest
from pathlib import Path
from commands.init import cmd_init
def test_init_success(tmp_path, monkeypatch):
"""测试初始化成功"""
monkeypatch.chdir(tmp_path)
result = cmd_init([])
assert result == 0
assert (tmp_path / ".aide").exists()
assert (tmp_path / ".aide" / "config.toml").exists()
assert (tmp_path / ".gitignore").exists()
def test_init_idempotent(tmp_path, monkeypatch):
"""测试幂等性"""
monkeypatch.chdir(tmp_path)
# 第一次初始化
result1 = cmd_init([])
assert result1 == 0
# 读取配置内容
config_path = tmp_path / ".aide" / "config.toml"
content1 = config_path.read_text()
# 第二次初始化
result2 = cmd_init([])
assert result2 == 0
# 验证配置未改变
content2 = config_path.read_text()
assert content1 == content2
def test_init_permission_error(tmp_path, monkeypatch):
"""测试权限错误"""
monkeypatch.chdir(tmp_path)
# 设置只读权限
tmp_path.chmod(0o444)
result = cmd_init([])
# 恢复权限
tmp_path.chmod(0o755)
assert result != 0
```
### 4.2 aide env 测试
```python
# tests/integration/test_env.py
import pytest
from commands.env import cmd_env, env_ensure_runtime, env_ensure_project
def test_env_ensure_runtime_success():
"""测试运行时环境检测成功"""
result = env_ensure_runtime()
assert result == 0
def test_env_ensure_project_success(tmp_path, monkeypatch):
"""测试项目环境检测成功"""
monkeypatch.chdir(tmp_path)
# 初始化项目
from commands.init import cmd_init
cmd_init([])
# 检测环境
result = env_ensure_project()
assert result == 0
def test_env_ensure_project_no_config(tmp_path, monkeypatch):
"""测试配置文件不存在"""
monkeypatch.chdir(tmp_path)
result = env_ensure_project()
assert result == 4
def test_env_ensure_create_venv(tmp_path, monkeypatch):
"""测试自动创建虚拟环境"""
monkeypatch.chdir(tmp_path)
# 初始化项目
from commands.init import cmd_init
cmd_init([])
# 检测环境(应该自动创建虚拟环境)
result = env_ensure_project()
assert result == 0
# 验证虚拟环境已创建
assert (tmp_path / ".venv").exists()
```
### 4.3 aide config 测试
```python
# tests/integration/test_config_cmd.py
import pytest
from commands.config_cmd import cmd_config, config_get, config_set
def test_config_get_success(tmp_path, monkeypatch, capsys):
"""测试获取配置成功"""
monkeypatch.chdir(tmp_path)
# 初始化
from commands.init import cmd_init
cmd_init([])
# 获取配置
result = config_get("task.source")
assert result == 0
captured = capsys.readouterr()
assert "task.source" in captured.out
assert "task-now.md" in captured.out
def test_config_set_success(tmp_path, monkeypatch):
"""测试设置配置成功"""
monkeypatch.chdir(tmp_path)
# 初始化
from commands.init import cmd_init
cmd_init([])
# 设置配置
result = config_set("task.source", "new-task.md")
assert result == 0
# 验证配置已更新
result = config_get("task.source")
assert result == 0
def test_config_cmd_no_args(capsys):
"""测试无参数"""
result = cmd_config([])
assert result == 2
captured = capsys.readouterr()
assert "缺少子命令" in captured.out
```
---
## 五、跨平台测试
### 5.1 路径处理测试
```python
def test_path_handling_cross_platform(tmp_path):
"""测试跨平台路径处理"""
from pathlib import Path
# 使用 pathlib 确保跨平台兼容
aide_dir = tmp_path / ".aide"
config_path = aide_dir / "config.toml"
aide_dir.mkdir()
config_path.write_text("test")
assert config_path.exists()
assert config_path.is_file()
```
### 5.2 命令执行测试
```python
@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
def test_unix_specific():
"""Unix 特定测试"""
pass
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
def test_windows_specific():
"""Windows 特定测试"""
pass
```
---
## 六、性能测试
### 6.1 执行时间测试
```python
import time
def test_init_performance(tmp_path, monkeypatch):
"""测试初始化性能"""
monkeypatch.chdir(tmp_path)
start = time.time()
result = cmd_init([])
elapsed = time.time() - start
assert result == 0
assert elapsed < 0.2 # 应该在 200ms 内完成
def test_config_get_performance(tmp_path, monkeypatch):
"""测试配置读取性能"""
monkeypatch.chdir(tmp_path)
# 初始化
cmd_init([])
# 测试性能
start = time.time()
result = config_get("task.source")
elapsed = time.time() - start
assert result == 0
assert elapsed < 0.1 # 应该在 100ms 内完成
```
### 6.2 内存占用测试
```python
import psutil
import os
def test_memory_usage():
"""测试内存占用"""
process = psutil.Process(os.getpid())
mem_before = process.memory_info().rss / 1024 / 1024 # MB
# 执行操作
cmd_init([])
mem_after = process.memory_info().rss / 1024 / 1024 # MB
mem_used = mem_after - mem_before
assert mem_used < 50 # 应该小于 50MB
```
---
## 七、测试覆盖率
### 7.1 覆盖率要求
- **核心模块**:≥ 80%
- **命令模块**:≥ 80%
- **工具模块**:≥ 70%
- **整体**:≥ 75%
### 7.2 运行覆盖率测试
```bash
# 运行测试并生成覆盖率报告
pytest --cov=src --cov-report=html --cov-report=term
# 查看覆盖率报告
open htmlcov/index.html
```
### 7.3 覆盖率配置
```ini
# .coveragerc
[run]
source = src
omit =
*/tests/*
*/venv/*
*/__pycache__/*
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
```
---
## 八、测试 Fixtures
### 8.1 conftest.py
```python
# tests/conftest.py
import pytest
from pathlib import Path
import shutil
@pytest.fixture
def temp_project(tmp_path):
"""创建临时项目目录"""
project_dir = tmp_path / "test_project"
project_dir.mkdir()
return project_dir
@pytest.fixture
def initialized_project(temp_project, monkeypatch):
"""创建已初始化的项目"""
monkeypatch.chdir(temp_project)
from commands.init import cmd_init
cmd_init([])
return temp_project
@pytest.fixture
def sample_config():
"""示例配置"""
return {
"task": {
"source": "task-now.md",
"spec": "task-spec.md"
},
"env": {
"python": {
"version": ">=3.10",
"venv": ".venv"
}
}
}
```
---
## 九、持续集成
### 9.1 GitHub Actions 配置
```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov pytest-mock
pip install tomli tomli-w
- name: Run tests
run: |
pytest --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
```
---
## 十、测试最佳实践
### 10.1 测试原则
1. **独立性**:每个测试独立运行,不依赖其他测试
2. **可重复性**:多次运行结果一致
3. **清晰性**:测试意图明确,易于理解
4. **快速性**:测试执行快速,及时反馈
### 10.2 测试模式
**AAA 模式**Arrange-Act-Assert
```python
def test_example():
# Arrange准备测试数据
config = {"task": {"source": "test.md"}}
# Act执行操作
result = get_config_value(config, "task.source")
# Assert验证结果
assert result == "test.md"
```
### 10.3 Mock 使用
```python
from unittest.mock import Mock, patch
def test_with_mock(monkeypatch):
"""使用 mock 测试"""
# Mock 函数
mock_func = Mock(return_value=True)
monkeypatch.setattr("module.function", mock_func)
# 执行测试
result = some_function()
# 验证 mock 被调用
mock_func.assert_called_once()
```
---
## 十一、测试检查清单
### 11.1 单元测试
- [ ] core/output.py 所有函数
- [ ] core/config.py 所有函数
- [ ] utils/validators.py 所有函数
- [ ] 边界条件测试
- [ ] 错误路径测试
### 11.2 集成测试
- [ ] aide init 完整流程
- [ ] aide env ensure --runtime
- [ ] aide env ensure
- [ ] aide config get
- [ ] aide config set
- [ ] 命令组合测试
### 11.3 跨平台测试
- [ ] Linux 测试通过
- [ ] macOS 测试通过
- [ ] Windows 测试通过
- [ ] 路径处理正确
### 11.4 性能测试
- [ ] 执行时间符合要求
- [ ] 内存占用符合要求
- [ ] 无内存泄漏
### 11.5 覆盖率
- [ ] 核心模块 ≥ 80%
- [ ] 命令模块 ≥ 80%
- [ ] 整体 ≥ 75%
---
## 十二、总结
### 12.1 核心要点
1. 完整的测试覆盖(单元、集成、跨平台)
2. 明确的覆盖率要求
3. 持续集成自动化
4. 遵循测试最佳实践
### 12.2 运行测试
```bash
# 运行所有测试
pytest
# 运行特定测试
pytest tests/unit/test_config.py
# 运行带覆盖率的测试
pytest --cov=src
# 运行性能测试
pytest -m performance
# 运行跨平台测试
pytest -m cross_platform
```
---
**版本**v1.0
**更新日期**2025-12-13