Files
agent-aide/aide-program/docs/07-测试规范.md
2025-12-13 04:37:41 +08:00

709 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 测试规范
## 一、测试概述
### 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