📃 docs: 部分完成
This commit is contained in:
708
aide-program/docs/07-测试规范.md
Normal file
708
aide-program/docs/07-测试规范.md
Normal file
@@ -0,0 +1,708 @@
|
||||
# 测试规范
|
||||
|
||||
## 一、测试概述
|
||||
|
||||
### 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
|
||||
Reference in New Issue
Block a user