08ea5fbe98
添加用户管理视图、API和状态管理文件
1178 lines
30 KiB
Markdown
1178 lines
30 KiB
Markdown
# 测试套件改进计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 将测试套件从当前77.8%覆盖率提升到金融级90%-95%标准,建立完整的测试金字塔,实现全生命周期质量保障
|
||
|
||
**Architecture:** 采用分层测试策略,优先补充高优先级覆盖率缺口,建立稳定可执行的测试基础设施,逐步引入性能、安全和集成测试
|
||
|
||
**Tech Stack:** Python (pytest, coverage), Playwright (TypeScript), Spring Boot (Java), Maven, Docker, CI/CD
|
||
|
||
---
|
||
|
||
## 改进阶段划分
|
||
|
||
### 阶段1: 高优先级修复(1-2周)- 覆盖率提升至85%+
|
||
### 阶段2: 中期优化(1-2个月)- 建立完整测试金字塔
|
||
### 阶段3: 长期建设(3-6个月)- 达到金融级合规标准
|
||
|
||
---
|
||
|
||
## 阶段1: 高优先级修复(1-2周)
|
||
|
||
### Task 1: 补充main.py测试
|
||
|
||
**Files:**
|
||
- Create: `api/tests/unit/test_main.py`
|
||
- Test: `api/tests/unit/test_main.py`
|
||
|
||
**Step 1: 分析main.py功能**
|
||
|
||
Read: `api/src/apitest/main.py`
|
||
|
||
Expected: 识别CLI入口点、参数解析、主流程
|
||
|
||
**Step 2: Write failing tests**
|
||
|
||
```python
|
||
# api/tests/unit/test_main.py
|
||
import pytest
|
||
from unittest.mock import patch, MagicMock
|
||
from apitest.main import main
|
||
import sys
|
||
|
||
def test_main_with_no_arguments():
|
||
"""测试无参数时显示帮助信息"""
|
||
with patch('sys.argv', ['main']):
|
||
with patch('builtins.print') as mock_print:
|
||
main()
|
||
# 验证调用了print
|
||
assert mock_print.called
|
||
|
||
def test_main_with_valid_command():
|
||
"""测试有效命令执行"""
|
||
with patch('sys.argv', ['main', 'run', '--config', 'test.yaml']):
|
||
with patch('apitest.main.TestEngine') as mock_engine:
|
||
with patch('apitest.main.load_config') as mock_config:
|
||
mock_config.return_value = {'test': 'config'}
|
||
main()
|
||
# 验证创建了TestEngine
|
||
assert mock_engine.called
|
||
|
||
def test_main_with_invalid_command():
|
||
"""测试无效命令处理"""
|
||
with patch('sys.argv', ['main', 'invalid']):
|
||
with pytest.raises(SystemExit):
|
||
main()
|
||
|
||
def test_main_with_config_file_not_found():
|
||
"""测试配置文件不存在"""
|
||
with patch('sys.argv', ['main', 'run', '--config', 'nonexistent.yaml']):
|
||
with patch('builtins.open', side_effect=FileNotFoundError):
|
||
with pytest.raises(SystemExit):
|
||
main()
|
||
```
|
||
|
||
**Step 3: Run test to verify it fails**
|
||
|
||
Run: `cd api && python -m pytest tests/unit/test_main.py -v`
|
||
|
||
Expected: FAIL with "module 'apitest.main' not found" or import errors
|
||
|
||
**Step 4: Implement minimal main.py structure**
|
||
|
||
Read: `api/src/apitest/main.py`
|
||
|
||
If file is minimal, add basic structure:
|
||
|
||
```python
|
||
# api/src/apitest/main.py
|
||
import sys
|
||
from apitest.cli_module import CLICommand
|
||
|
||
def main():
|
||
"""主入口函数"""
|
||
if len(sys.argv) < 2:
|
||
print("Usage: python -m apitest.main <command> [options]")
|
||
print("Commands: run, test, report")
|
||
sys.exit(1)
|
||
|
||
command = sys.argv[1]
|
||
cli = CLICommand()
|
||
|
||
if command == 'run':
|
||
cli.run_tests()
|
||
elif command == 'test':
|
||
cli.run_specific_test()
|
||
elif command == 'report':
|
||
cli.generate_report()
|
||
else:
|
||
print(f"Unknown command: {command}")
|
||
sys.exit(1)
|
||
|
||
if __name__ == '__main__':
|
||
main()
|
||
```
|
||
|
||
**Step 5: Run test to verify it passes**
|
||
|
||
Run: `cd api && python -m pytest tests/unit/test_main.py -v`
|
||
|
||
Expected: PASS
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add api/tests/unit/test_main.py api/src/apitest/main.py
|
||
git commit -m "test: add main.py unit tests and basic CLI structure"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: 提升cli_module.py覆盖率到80%+
|
||
|
||
**Files:**
|
||
- Modify: `api/tests/unit/test_cli.py`
|
||
- Test: `api/tests/unit/test_cli.py`
|
||
|
||
**Step 1: 分析未覆盖代码行**
|
||
|
||
Run: `cd api && python -m pytest tests/unit/test_cli.py --cov=src/apitest/cli_module --cov-report=term-missing`
|
||
|
||
Expected: 识别未覆盖的代码行(52, 61-93, 96-100, 131, 134-136, 158, 160, 162, 164, 167-170, 196-198, 223)
|
||
|
||
**Step 2: Write tests for missing branches**
|
||
|
||
```python
|
||
# api/tests/unit/test_cli.py - 补充测试
|
||
def test_cli_run_with_invalid_config():
|
||
"""测试无效配置文件"""
|
||
cli = CLICommand()
|
||
with pytest.raises(Exception):
|
||
cli.run_tests(config='invalid.yaml')
|
||
|
||
def test_cli_run_with_missing_test_cases():
|
||
"""测试缺少测试用例"""
|
||
cli = CLICommand()
|
||
with patch('os.path.exists', return_value=False):
|
||
with pytest.raises(FileNotFoundError):
|
||
cli.run_tests()
|
||
|
||
def test_cli_report_generation():
|
||
"""测试报告生成功能"""
|
||
cli = CLICommand()
|
||
with patch('apitest.cli_module.ReportManager') as mock_report:
|
||
cli.generate_report()
|
||
assert mock_report.generate.called
|
||
|
||
def test_cli_error_handling():
|
||
"""测试错误处理逻辑"""
|
||
cli = CLICommand()
|
||
with patch.object(cli, 'run_tests', side_effect=Exception("Test error")):
|
||
with pytest.raises(Exception):
|
||
cli.run_tests()
|
||
```
|
||
|
||
**Step 3: Run test to verify coverage improvement**
|
||
|
||
Run: `cd api && python -m pytest tests/unit/test_cli.py --cov=src/apitest/cli_module --cov-report=term-missing`
|
||
|
||
Expected: Coverage increases from 72.6% to 80%+
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add api/tests/unit/test_cli.py
|
||
git commit -m "test: improve cli_module.py coverage to 80%+"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 修复API服务启动问题
|
||
|
||
**Files:**
|
||
- Modify: `everything-is-suitable-api/everything-is-suitable-app/src/main/resources/application.yml`
|
||
- Modify: `everything-is-suitable-api/everything-is-suitable-app/pom.xml`
|
||
- Test: Manual verification
|
||
|
||
**Step 1: 分析bean冲突原因**
|
||
|
||
Run: `cd everything-is-suitable-api && mvn dependency:tree | grep jacksonConfig`
|
||
|
||
Expected: 识别哪个模块定义了jacksonConfig bean
|
||
|
||
**Step 2: 检查Spring Boot配置**
|
||
|
||
Read: `everything-is-suitable-api/everything-is-suitable-app/src/main/resources/application.yml`
|
||
|
||
Look for bean configuration and component scanning
|
||
|
||
**Step 3: 解决bean名称冲突**
|
||
|
||
Option A: 重命名冲突的bean
|
||
```java
|
||
// everything-is-suitable-api/everything-is-suitable-base/src/main/java/.../JacksonConfig.java
|
||
@Configuration
|
||
public class BaseJacksonConfig { // 重命名
|
||
// configuration
|
||
}
|
||
```
|
||
|
||
Option B: 使用@Primary注解
|
||
```java
|
||
@Configuration
|
||
@Primary
|
||
public class AppJacksonConfig { // 标记为首选
|
||
// configuration
|
||
}
|
||
```
|
||
|
||
**Step 4: 验证服务启动**
|
||
|
||
Run: `cd everything-is-suitable-api/everything-is-suitable-app && mvn spring-boot:run -Dspring-boot.run.profiles=dev`
|
||
|
||
Expected: Service starts successfully on port 8080
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add everything-is-suitable-api/
|
||
git commit -m "fix: resolve jacksonConfig bean name conflict"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: 修复E2E测试模块引用问题
|
||
|
||
**Files:**
|
||
- Modify: `e2e/examples/admin-role-management.spec.ts`
|
||
- Modify: `e2e/examples/admin-user-management.spec.ts`
|
||
- Test: `e2e/integration/login-mock.spec.ts`
|
||
|
||
**Step 1: 分析fixtures路径错误**
|
||
|
||
Read: `e2e/examples/admin-role-management.spec.ts:1`
|
||
|
||
Expected: `import { test, expect } from './fixtures/test-fixtures';`
|
||
|
||
**Step 2: 修复import路径**
|
||
|
||
```typescript
|
||
// e2e/examples/admin-role-management.spec.ts
|
||
// 修改为正确的相对路径
|
||
import { test, expect } from '../fixtures/test-fixtures';
|
||
```
|
||
|
||
**Step 3: 验证所有测试文件**
|
||
|
||
Run: `cd e2e && grep -r "from './fixtures/test-fixtures'" examples/`
|
||
|
||
Expected: 找到所有需要修复的文件
|
||
|
||
**Step 4: 批量修复**
|
||
|
||
Run: `cd e2e && find examples/ -name "*.spec.ts" -exec sed -i '' "s|from './fixtures/test-fixtures'|from '../fixtures/test-fixtures'|g" {} \;`
|
||
|
||
**Step 5: 验证测试可执行**
|
||
|
||
Run: `cd e2e && ADMIN_BASE_URL=http://localhost:5173 npx playwright test examples/ --list`
|
||
|
||
Expected: No module import errors
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add e2e/examples/
|
||
git commit -m "fix: correct fixtures import paths in example tests"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 建立测试环境管理脚本
|
||
|
||
**Files:**
|
||
- Create: `scripts/start-test-env.sh`
|
||
- Create: `scripts/stop-test-env.sh`
|
||
- Create: `scripts/check-services.sh`
|
||
|
||
**Step 1: 创建服务启动脚本**
|
||
|
||
```bash
|
||
# scripts/start-test-env.sh
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
echo "========================================="
|
||
echo " 启动测试环境"
|
||
echo "========================================"
|
||
|
||
# 检查端口占用
|
||
check_port() {
|
||
local port=$1
|
||
if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1; then
|
||
echo "端口 $port 已被占用"
|
||
return 1
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# 启动API服务
|
||
echo "----------------------------------------"
|
||
echo " 启动 API 服务..."
|
||
echo "----------------------------------------"
|
||
if check_port 8080; then
|
||
echo "API服务已在运行"
|
||
else
|
||
cd everything-is-suitable-api/everything-is-suitable-app
|
||
nohup mvn spring-boot:run -Dspring-boot.run.profiles=dev > /tmp/api.log 2>&1 &
|
||
echo "API服务启动中..."
|
||
sleep 30
|
||
fi
|
||
|
||
# 启动Admin服务
|
||
echo "----------------------------------------"
|
||
echo " 启动 Admin 服务..."
|
||
echo "----------------------------------------"
|
||
if check_port 5173; then
|
||
echo "Admin服务已在运行"
|
||
else
|
||
cd everything-is-suitable-admin
|
||
nohup npm run dev > /tmp/admin.log 2>&1 &
|
||
echo "Admin服务启动中..."
|
||
sleep 10
|
||
fi
|
||
|
||
# 验证服务健康
|
||
echo "----------------------------------------"
|
||
echo " 验证服务健康..."
|
||
echo "----------------------------------------"
|
||
bash scripts/check-services.sh
|
||
|
||
echo "========================================="
|
||
echo " ✅ 所有服务启动成功!"
|
||
echo "========================================="
|
||
```
|
||
|
||
**Step 2: 创建服务停止脚本**
|
||
|
||
```bash
|
||
# scripts/stop-test-env.sh
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
echo "========================================="
|
||
echo " 停止测试环境"
|
||
echo "========================================"
|
||
|
||
# 停止API服务
|
||
echo "停止 API 服务..."
|
||
pkill -f "everything-is-suitable-app"
|
||
echo "API服务已停止"
|
||
|
||
# 停止Admin服务
|
||
echo "停止 Admin 服务..."
|
||
pkill -f "vite.*5173"
|
||
echo "Admin服务已停止"
|
||
|
||
echo "========================================="
|
||
echo " ✅ 所有服务已停止"
|
||
echo "========================================="
|
||
```
|
||
|
||
**Step 3: 创建服务检查脚本**
|
||
|
||
```bash
|
||
# scripts/check-services.sh
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
echo "========================================="
|
||
echo " 服务状态检查"
|
||
echo "========================================="
|
||
|
||
# 检查API服务
|
||
echo "API 服务:"
|
||
if curl -s http://localhost:8080/api/health > /dev/null 2>&1; then
|
||
echo " ✅ 运行中 (http://localhost:8080/api/health)"
|
||
else
|
||
echo " ❌ 未运行"
|
||
fi
|
||
|
||
# 检查Admin服务
|
||
echo "Admin 服务:"
|
||
if curl -s http://localhost:5173 > /dev/null 2>&1; then
|
||
echo " ✅ 运行中 (http://localhost:5173)"
|
||
else
|
||
echo " ❌ 未运行"
|
||
fi
|
||
|
||
echo "========================================="
|
||
```
|
||
|
||
**Step 4: 添加执行权限**
|
||
|
||
Run: `chmod +x scripts/start-test-env.sh scripts/stop-test-env.sh scripts/check-services.sh`
|
||
|
||
**Step 5: 测试脚本**
|
||
|
||
Run: `bash scripts/check-services.sh`
|
||
|
||
Expected: 显示服务状态
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add scripts/
|
||
git commit -m "feat: add test environment management scripts"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段2: 中期优化(1-2个月)
|
||
|
||
### Task 6: 增加集成测试覆盖
|
||
|
||
**Files:**
|
||
- Create: `api/tests/integration/`
|
||
- Create: `api/tests/integration/test_api_integration.py`
|
||
|
||
**Step 1: 设计集成测试场景**
|
||
|
||
```python
|
||
# api/tests/integration/test_api_integration.py
|
||
import pytest
|
||
import requests
|
||
from apitest.client.api_client import APIClient
|
||
from apitest.client.auth_manager import AuthManager
|
||
|
||
@pytest.fixture(scope="module")
|
||
def api_client():
|
||
"""API客户端fixture"""
|
||
return APIClient("http://localhost:8080")
|
||
|
||
@pytest.fixture(scope="module")
|
||
def auth_manager():
|
||
"""认证管理器fixture"""
|
||
return AuthManager(
|
||
"http://localhost:8080",
|
||
{"username": "admin", "password": "admin123"},
|
||
None
|
||
)
|
||
|
||
def test_api_integration_full_workflow(api_client, auth_manager):
|
||
"""测试完整API集成工作流"""
|
||
# 1. 登录
|
||
login_result = auth_manager.login()
|
||
assert login_result["data"]["token"]
|
||
|
||
# 2. 使用token访问受保护资源
|
||
api_client.set_auth_token(auth_manager._token)
|
||
result = api_client.get("/sys/user/list")
|
||
assert result["status_code"] == 200
|
||
|
||
# 3. 登出
|
||
auth_manager.logout()
|
||
assert auth_manager._token is None
|
||
```
|
||
|
||
**Step 2: 创建集成测试配置**
|
||
|
||
```python
|
||
# api/tests/integration/conftest.py
|
||
import pytest
|
||
|
||
def pytest_configure(config):
|
||
"""集成测试配置"""
|
||
config.addinivalue_marker(
|
||
"integration",
|
||
"标记为集成测试,需要真实服务"
|
||
)
|
||
```
|
||
|
||
**Step 3: 运行集成测试**
|
||
|
||
Run: `cd api && python -m pytest tests/integration/ -v -m integration`
|
||
|
||
Expected: PASS (需要服务运行)
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add api/tests/integration/
|
||
git commit -m "test: add API integration tests"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 7: 补充E2E业务流程测试
|
||
|
||
**Files:**
|
||
- Create: `e2e/integration/user-workflow.spec.ts`
|
||
- Create: `e2e/integration/role-workflow.spec.ts`
|
||
|
||
**Step 1: 设计用户管理工作流测试**
|
||
|
||
```typescript
|
||
// e2e/integration/user-workflow.spec.ts
|
||
import { test, expect } from '@playwright/test';
|
||
|
||
const BASE_URL = process.env.ADMIN_BASE_URL || 'http://localhost:5173';
|
||
|
||
test.describe('用户管理工作流E2E测试', () => {
|
||
|
||
test('完整用户生命周期', async ({ page }) => {
|
||
await page.goto(`${BASE_URL}/login`);
|
||
|
||
// 登录
|
||
await page.fill('[data-testid="username-input"]', 'admin');
|
||
await page.fill('[data-testid="password-input"]', 'admin123');
|
||
await page.click('[data-testid="login-button"]');
|
||
await expect(page).toHaveURL(/.*\//);
|
||
|
||
// 创建用户
|
||
await page.goto(`${BASE_URL}/system/user`);
|
||
await page.click('button:has-text("新增")');
|
||
await page.fill('[data-testid="username-input"]', 'testuser');
|
||
await page.fill('[data-testid="email-input"]', 'test@example.com');
|
||
await page.click('button:has-text("保存")');
|
||
await expect(page.locator('text=保存成功')).toBeVisible();
|
||
|
||
// 编辑用户
|
||
await page.click('td:has-text("testuser") button:has-text("编辑")');
|
||
await page.fill('[data-testid="email-input"]', 'updated@example.com');
|
||
await page.click('button:has-text("保存")');
|
||
await expect(page.locator('text=保存成功')).toBeVisible();
|
||
|
||
// 删除用户
|
||
await page.click('td:has-text("testuser") button:has-text("删除")');
|
||
await page.click('button:has-text("确认")');
|
||
await expect(page.locator('text=删除成功')).toBeVisible();
|
||
});
|
||
});
|
||
```
|
||
|
||
**Step 2: 运行工作流测试**
|
||
|
||
Run: `cd e2e && ADMIN_BASE_URL=http://localhost:5173 npx playwright test integration/user-workflow.spec.ts --headed`
|
||
|
||
Expected: PASS (需要完整服务)
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add e2e/integration/
|
||
git commit -m "test: add user and role workflow E2E tests"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: 建立测试数据管理
|
||
|
||
**Files:**
|
||
- Create: `api/tests/factories/test_data_factory.py`
|
||
- Modify: `api/tests/conftest.py`
|
||
|
||
**Step 1: 创建测试数据工厂**
|
||
|
||
```python
|
||
# api/tests/factories/test_data_factory.py
|
||
from faker import Faker
|
||
|
||
fake = Faker('zh_CN')
|
||
|
||
class TestDataFactory:
|
||
"""测试数据工厂"""
|
||
|
||
@staticmethod
|
||
def create_user():
|
||
"""创建测试用户数据"""
|
||
return {
|
||
"username": fake.user_name(),
|
||
"email": fake.email(),
|
||
"phone": fake.phone_number(),
|
||
"password": fake.password(length=12),
|
||
}
|
||
|
||
@staticmethod
|
||
def create_role():
|
||
"""创建测试角色数据"""
|
||
return {
|
||
"roleName": fake.job(),
|
||
"description": fake.sentence(),
|
||
"permissions": ["user:read", "user:write"],
|
||
}
|
||
|
||
@staticmethod
|
||
def create_menu():
|
||
"""创建测试菜单数据"""
|
||
return {
|
||
"menuName": fake.word(),
|
||
"menuType": "directory",
|
||
"path": f"/{fake.word()}",
|
||
"icon": fake.word(),
|
||
}
|
||
```
|
||
|
||
**Step 2: 集成到conftest**
|
||
|
||
```python
|
||
# api/tests/conftest.py
|
||
import pytest
|
||
from tests.factories.test_data_factory import TestDataFactory
|
||
|
||
@pytest.fixture
|
||
def test_user():
|
||
"""测试用户数据fixture"""
|
||
return TestDataFactory.create_user()
|
||
|
||
@pytest.fixture
|
||
def test_role():
|
||
"""测试角色数据fixture"""
|
||
return TestDataFactory.create_role()
|
||
```
|
||
|
||
**Step 3: 使用测试数据工厂**
|
||
|
||
```python
|
||
# 在测试中使用
|
||
def test_create_user_with_factory(test_user):
|
||
"""使用工厂创建测试用户"""
|
||
result = api_client.post("/sys/user", body=test_user)
|
||
assert result["status_code"] == 201
|
||
```
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add api/tests/factories/
|
||
git commit -m "feat: add test data factory for better test maintainability"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段3: 长期建设(3-6个月)
|
||
|
||
### Task 9: 引入性能测试
|
||
|
||
**Files:**
|
||
- Create: `performance/load-test.jmx`
|
||
- Create: `performance/run-load-test.sh`
|
||
|
||
**Step 1: 创建JMeter负载测试计划**
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<jmeterTestPlan version="1.2">
|
||
<hashTree>
|
||
<TestPlan guiclass="TestPlan" testclass="TestPlan" testname="API Load Test">
|
||
<elementProp name="TestPlan.user_define_classpath" elementType="TestPlan"
|
||
guiclass="TestPlan" testclass="TestPlan" testname="TestPlan.user_define_classpath">
|
||
<stringProp name="TestPlan.user_define_classpath" value=""/>
|
||
</elementProp>
|
||
|
||
<hashTree>
|
||
<ThreadGroup guiclass="ThreadGroup" testclass="ThreadGroup"
|
||
testname="Thread Group" sampler="LoopController">
|
||
<stringProp name="ThreadGroup.num_threads" value="100"/>
|
||
<stringProp name="ThreadGroup.ramp_time" value="10"/>
|
||
<stringProp name="ThreadGroup.duration" value="60"/>
|
||
|
||
<HTTPSamplerProxy guiclass="HTTPSamplerProxy" testclass="HTTPSamplerProxy"
|
||
testname="Login Request">
|
||
<stringProp name="HTTPSampler.domain" value="localhost"/>
|
||
<stringProp name="HTTPSampler.port" value="8080"/>
|
||
<stringProp name="HTTPSampler.path" value="/api/sys/auth/login"/>
|
||
<stringProp name="HTTPSampler.method" value="POST"/>
|
||
<elementProp name="HTTPsampler.Arguments" elementType="HTTPArguments">
|
||
<collectionProp name="HTTPsampler.Arguments.arguments">
|
||
<elementProp name="HTTPArgument" elementType="HTTPArgument">
|
||
<stringProp name="Argument.name" value="username"/>
|
||
<stringProp name="Argument.value" value="admin"/>
|
||
</elementProp>
|
||
<elementProp name="HTTPArgument" elementType="HTTPArgument">
|
||
<stringProp name="Argument.name" value="password"/>
|
||
<stringProp name="Argument.value" value="admin123"/>
|
||
</elementProp>
|
||
</collectionProp>
|
||
</elementProp>
|
||
</HTTPSamplerProxy>
|
||
</ThreadGroup>
|
||
</hashTree>
|
||
</hashTree>
|
||
</hashTree>
|
||
</jmeterTestPlan>
|
||
```
|
||
|
||
**Step 2: 创建性能测试执行脚本**
|
||
|
||
```bash
|
||
# performance/run-load-test.sh
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
echo "========================================="
|
||
echo " 运行负载测试"
|
||
echo "========================================="
|
||
|
||
# 检查JMeter安装
|
||
if ! command -v jmeter &> /dev/null; then
|
||
echo "JMeter未安装,请先安装JMeter"
|
||
exit 1
|
||
fi
|
||
|
||
# 运行负载测试
|
||
jmeter -n -t performance/load-test.jmx \
|
||
-l performance/results.jtl \
|
||
-e -o performance/report \
|
||
-Jusers=100 \
|
||
-Jrampup=10 \
|
||
-Jduration=60
|
||
|
||
echo "========================================="
|
||
echo " 负载测试完成"
|
||
echo "========================================="
|
||
echo "报告位置: performance/report/index.html"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add performance/
|
||
git commit -m "feat: add performance testing with JMeter"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 10: 添加安全测试
|
||
|
||
**Files:**
|
||
- Create: `security/security-test.py`
|
||
- Create: `security/run-security-scan.sh`
|
||
|
||
**Step 1: 创建安全测试脚本**
|
||
|
||
```python
|
||
# security/security-test.py
|
||
import requests
|
||
import pytest
|
||
|
||
class TestSecurity:
|
||
"""安全测试套件"""
|
||
|
||
BASE_URL = "http://localhost:8080"
|
||
|
||
def test_sql_injection_protection(self):
|
||
"""测试SQL注入防护"""
|
||
malicious_payloads = [
|
||
"' OR '1'='1",
|
||
"admin'--",
|
||
"' UNION SELECT * FROM users--",
|
||
]
|
||
|
||
for payload in malicious_payloads:
|
||
response = requests.post(
|
||
f"{self.BASE_URL}/api/sys/auth/login",
|
||
json={"username": payload, "password": "test"}
|
||
)
|
||
# 应该返回401或400,而不是500
|
||
assert response.status_code in [400, 401]
|
||
|
||
def test_xss_protection(self):
|
||
"""测试XSS防护"""
|
||
xss_payloads = [
|
||
"<script>alert('xss')</script>",
|
||
"<img src=x onerror=alert('xss')>",
|
||
"javascript:alert('xss')",
|
||
]
|
||
|
||
for payload in xss_payloads:
|
||
response = requests.post(
|
||
f"{self.BASE_URL}/api/sys/user",
|
||
json={"username": payload}
|
||
)
|
||
# 应该过滤或转义
|
||
assert response.status_code in [400, 403]
|
||
|
||
def test_authentication_bypass(self):
|
||
"""测试认证绕过"""
|
||
# 测试无token访问
|
||
response = requests.get(f"{self.BASE_URL}/api/sys/user/list")
|
||
assert response.status_code == 401
|
||
|
||
# 测试过期token
|
||
response = requests.get(
|
||
f"{self.BASE_URL}/api/sys/user/list",
|
||
headers={"Authorization": "Bearer expired_token"}
|
||
)
|
||
assert response.status_code == 401
|
||
|
||
def test_sensitive_data_exposure(self):
|
||
"""测试敏感数据暴露"""
|
||
# 测试错误响应不应包含敏感信息
|
||
response = requests.post(
|
||
f"{self.BASE_URL}/api/sys/auth/login",
|
||
json={"username": "wrong", "password": "wrong"}
|
||
)
|
||
assert response.status_code == 401
|
||
# 验证不暴露数据库错误、堆栈信息等
|
||
assert "database" not in response.text.lower()
|
||
assert "stack" not in response.text.lower()
|
||
```
|
||
|
||
**Step 2: 创建安全扫描脚本**
|
||
|
||
```bash
|
||
# security/run-security-scan.sh
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
echo "========================================="
|
||
echo " 运行安全扫描"
|
||
echo "========================================="
|
||
|
||
# 检查OWASP ZAP安装
|
||
if ! command -v zap-cli &> /dev/null; then
|
||
echo "OWASP ZAP未安装,请先安装ZAP"
|
||
exit 1
|
||
fi
|
||
|
||
# 运行ZAP扫描
|
||
zap-cli quick-scan \
|
||
-t http://localhost:5173 \
|
||
-r security/zap-report.html \
|
||
-x
|
||
|
||
echo "========================================="
|
||
echo " 安全扫描完成"
|
||
echo "========================================="
|
||
echo "报告位置: security/zap-report.html"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add security/
|
||
git commit -m "feat: add security testing suite"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 11: 实施AI辅助测试
|
||
|
||
**Files:**
|
||
- Create: `ai/test-generator.py`
|
||
- Create: `ai/defect-predictor.py`
|
||
|
||
**Step 1: 创建测试用例生成器**
|
||
|
||
```python
|
||
# ai/test-generator.py
|
||
import openai
|
||
from typing import List, Dict
|
||
|
||
class TestGenerator:
|
||
"""AI辅助测试用例生成器"""
|
||
|
||
def __init__(self, api_key: str):
|
||
self.client = openai.OpenAI(api_key=api_key)
|
||
|
||
def generate_test_cases(self, function_code: str) -> List[Dict]:
|
||
"""基于函数代码生成测试用例"""
|
||
prompt = f"""
|
||
基于以下函数代码,生成全面的测试用例:
|
||
|
||
{function_code}
|
||
|
||
要求:
|
||
1. 生成正常路径测试
|
||
2. 生成边界条件测试
|
||
3. 生成异常情况测试
|
||
4. 每个测试用例包含:测试名称、输入、预期输出
|
||
|
||
返回JSON格式的测试用例列表。
|
||
"""
|
||
|
||
response = self.client.chat.completions.create(
|
||
model="gpt-4",
|
||
messages=[{"role": "user", "content": prompt}],
|
||
temperature=0.7
|
||
)
|
||
|
||
content = response.choices[0].message.content
|
||
return eval(content) # 简化,实际应使用JSON解析
|
||
|
||
def generate_test_code(self, test_case: Dict) -> str:
|
||
"""生成测试代码"""
|
||
prompt = f"""
|
||
基于以下测试用例,生成pytest测试代码:
|
||
|
||
测试名称: {test_case['name']}
|
||
输入: {test_case['input']}
|
||
预期输出: {test_case['expected']}
|
||
|
||
要求:
|
||
1. 使用pytest框架
|
||
2. 包含适当的断言
|
||
3. 添加必要的mock
|
||
"""
|
||
|
||
response = self.client.chat.completions.create(
|
||
model="gpt-4",
|
||
messages=[{"role": "user", "content": prompt}],
|
||
temperature=0.3
|
||
)
|
||
|
||
return response.choices[0].message.content
|
||
```
|
||
|
||
**Step 2: 创建缺陷预测器**
|
||
|
||
```python
|
||
# ai/defect-predictor.py
|
||
from sklearn.ensemble import RandomForestClassifier
|
||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||
import pandas as pd
|
||
|
||
class DefectPredictor:
|
||
"""基于历史数据的缺陷预测器"""
|
||
|
||
def __init__(self):
|
||
self.model = RandomForestClassifier(n_estimators=100)
|
||
self.vectorizer = TfidfVectorizer(max_features=1000)
|
||
|
||
def train(self, historical_data: pd.DataFrame):
|
||
"""训练模型"""
|
||
X = self.vectorizer.fit_transform(historical_data['code_changes'])
|
||
y = historical_data['has_defect']
|
||
self.model.fit(X, y)
|
||
|
||
def predict(self, code_changes: str) -> float:
|
||
"""预测缺陷概率"""
|
||
X = self.vectorizer.transform([code_changes])
|
||
probability = self.model.predict_proba(X)[0][1]
|
||
return probability
|
||
|
||
def get_risk_level(self, probability: float) -> str:
|
||
"""获取风险等级"""
|
||
if probability > 0.7:
|
||
return "高风险"
|
||
elif probability > 0.4:
|
||
return "中风险"
|
||
else:
|
||
return "低风险"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add ai/
|
||
git commit -m "feat: add AI-assisted testing tools"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 12: 建立CI/CD集成
|
||
|
||
**Files:**
|
||
- Create: `.github/workflows/test.yml`
|
||
- Create: `.github/workflows/quality-gate.yml`
|
||
|
||
**Step 1: 创建测试工作流**
|
||
|
||
```yaml
|
||
# .github/workflows/test.yml
|
||
name: Test Suite
|
||
|
||
on:
|
||
push:
|
||
branches: [ main, develop ]
|
||
pull_request:
|
||
branches: [ main ]
|
||
|
||
jobs:
|
||
api-tests:
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Set up Python
|
||
uses: actions/setup-python@v4
|
||
with:
|
||
python-version: '3.13'
|
||
|
||
- name: Install dependencies
|
||
run: |
|
||
cd everything-is-suitable-test/api
|
||
pip install -r requirements.txt
|
||
|
||
- name: Run API tests
|
||
run: |
|
||
cd everything-is-suitable-test/api
|
||
python -m pytest tests/ --cov=src/apitest --cov-report=xml
|
||
|
||
- name: Upload coverage
|
||
uses: codecov/codecov-action@v3
|
||
with:
|
||
files: ./everything-is-suitable-test/api/coverage.xml
|
||
|
||
- name: Check coverage threshold
|
||
run: |
|
||
coverage=$(python -c "import json; print(json.load(open('coverage.json'))['totals']['percent_covered'])")
|
||
if (( $(echo "$coverage < 85" | bc -l) )); then
|
||
echo "Coverage $coverage% is below threshold 85%"
|
||
exit 1
|
||
fi
|
||
|
||
e2e-tests:
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Set up Node.js
|
||
uses: actions/setup-node@v3
|
||
with:
|
||
node-version: '18'
|
||
|
||
- name: Install dependencies
|
||
run: |
|
||
cd everything-is-suitable-test/e2e
|
||
npm ci
|
||
|
||
- name: Install Playwright
|
||
run: npx playwright install --with-deps
|
||
|
||
- name: Run E2E tests
|
||
run: |
|
||
cd everything-is-suitable-test/e2e
|
||
npx playwright test
|
||
|
||
- name: Upload test results
|
||
uses: actions/upload-artifact@v3
|
||
if: always()
|
||
with:
|
||
name: playwright-report
|
||
path: everything-is-suitable-test/e2e/playwright-report/
|
||
```
|
||
|
||
**Step 2: 创建质量门禁工作流**
|
||
|
||
```yaml
|
||
# .github/workflows/quality-gate.yml
|
||
name: Quality Gate
|
||
|
||
on:
|
||
pull_request:
|
||
branches: [ main ]
|
||
|
||
jobs:
|
||
quality-check:
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Run linting
|
||
run: |
|
||
cd everything-is-suitable-test/api
|
||
flake8 src/apitest --max-line-length=100
|
||
|
||
- name: Run type checking
|
||
run: |
|
||
cd everything-is-suitable-test/api
|
||
mypy src/apitest --strict
|
||
|
||
- name: Check coverage
|
||
run: |
|
||
cd everything-is-suitable-test/api
|
||
python -m pytest tests/ --cov=src/apitest --cov-fail-under=85
|
||
|
||
- name: Security scan
|
||
run: |
|
||
bandit -r src/apitest -f json -o security-report.json
|
||
|
||
- name: Upload reports
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: quality-reports
|
||
path: |
|
||
everything-is-suitable-test/api/security-report.json
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add .github/workflows/
|
||
git commit -m "ci: add CI/CD pipelines with quality gates"
|
||
```
|
||
|
||
---
|
||
|
||
## 执行检查清单
|
||
|
||
### 阶段1完成标准
|
||
- [ ] main.py测试覆盖率 > 80%
|
||
- [ ] cli_module.py测试覆盖率 > 80%
|
||
- [ ] 整体API测试覆盖率 > 85%
|
||
- [ ] API服务可正常启动
|
||
- [ ] 所有E2E测试可执行
|
||
- [ ] 测试环境管理脚本可用
|
||
|
||
### 阶段2完成标准
|
||
- [ ] 集成测试覆盖核心业务流程
|
||
- [ ] E2E测试覆盖完整用户场景
|
||
- [ ] 测试数据工厂投入使用
|
||
- [ ] 测试类型分布均衡
|
||
|
||
### 阶段3完成标准
|
||
- [ ] 性能测试可执行
|
||
- [ ] 安全测试可执行
|
||
- [ ] AI辅助工具可用
|
||
- [ ] CI/CD流水线运行
|
||
- [ ] 质量门禁生效
|
||
- [ ] 整体覆盖率 > 90%
|
||
|
||
---
|
||
|
||
## 风险与缓解
|
||
|
||
### 风险1: API服务bean冲突
|
||
**缓解**: 优先解决Task 3,使用@Primary注解或重命名bean
|
||
|
||
### 风险2: E2E测试依赖后端
|
||
**缓解**: 使用Mock测试先行,真实后端测试作为补充
|
||
|
||
### 风险3: 性能测试环境要求
|
||
**缓解**: 使用轻量级负载测试,避免影响开发环境
|
||
|
||
### 风险4: AI工具成本
|
||
**缓解**: 使用开源模型或限制API调用次数
|
||
|
||
---
|
||
|
||
## 成功指标
|
||
|
||
### 阶段1指标
|
||
- 整体覆盖率: 77.8% → 85%+
|
||
- 测试通过率: 100% (保持)
|
||
- E2E可执行性: 8/87 → 87/87
|
||
|
||
### 阶段2指标
|
||
- 集成测试: 0 → 20+
|
||
- E2E业务流程: 8 → 30+
|
||
- 测试数据工厂: 0 → 1
|
||
|
||
### 阶段3指标
|
||
- 性能测试: 0 → 1个套件
|
||
- 安全测试: 0 → 1个套件
|
||
- AI辅助工具: 0 → 2个工具
|
||
- CI/CD: 0 → 2个流水线
|
||
- 整体覆盖率: 85% → 90%+
|
||
|
||
---
|
||
|
||
**计划创建时间**: 2026-03-07
|
||
**计划创建人**: 张翔(资深金融级高级自动化测试工程师)
|
||
**预计完成时间**: 6个月
|
||
**执行方法**: TDD + 频繁提交 + 持续验证 |