08ea5fbe98
添加用户管理视图、API和状态管理文件
681 lines
20 KiB
Markdown
681 lines
20 KiB
Markdown
# 统一测试框架架构设计
|
||
|
||
## 设计目标
|
||
|
||
1. **统一测试框架**:整合多个测试框架,提供统一的配置、执行和报告机制
|
||
2. **提升代码复用性**:消除重复代码,提取公共测试工具类和配置
|
||
3. **优化测试流程**:简化测试执行,提高测试效率和稳定性
|
||
4. **聚焦单元/集成测试**:重点关注单元测试和集成测试
|
||
5. **混合技术栈**:Playwright用于E2E,Python pytest用于API测试
|
||
|
||
## 架构设计
|
||
|
||
### 1. 整体架构
|
||
|
||
```
|
||
everything-is-suitable-test/ # 统一测试框架根目录
|
||
│
|
||
├── e2e/ # E2E测试层(Playwright + TypeScript)
|
||
│ ├── core/ # 核心模块
|
||
│ │ ├── test-config.ts # 统一配置管理
|
||
│ │ ├── test-logger.ts # 统一日志记录
|
||
│ │ ├── test-reporter.ts # 统一报告生成
|
||
│ │ └── test-data-manager.ts # 统一数据管理
|
||
│ ├── helpers/ # 测试辅助工具
|
||
│ │ ├── form-helper.ts # 表单操作辅助
|
||
│ │ ├── table-helper.ts # 表格操作辅助
|
||
│ │ ├── screenshot-helper.ts # 截图辅助
|
||
│ │ ├── api-helper.ts # API请求辅助
|
||
│ │ └── assertion-helper.ts # 断言辅助
|
||
│ ├── pages/ # 页面对象模型(POM)
|
||
│ │ ├── base-page.ts # 基础页面类
|
||
│ │ ├── login-page.ts # 登录页面
|
||
│ │ ├── dashboard-page.ts # 仪表盘页面
|
||
│ │ ├── user-management-page.ts # 用户管理页面
|
||
│ │ └── role-management-page.ts # 角色管理页面
|
||
│ ├── fixtures/ # 测试夹具
|
||
│ │ └── test-fixtures.ts # 自定义测试夹具
|
||
│ └── tests/ # E2E测试用例
|
||
│ ├── admin/ # 管理系统E2E测试
|
||
│ │ ├── auth.spec.ts
|
||
│ │ ├── user-management.spec.ts
|
||
│ │ └── role-management.spec.ts
|
||
│ ├── uniapp/ # UniApp E2E测试
|
||
│ │ ├── calendar.spec.ts
|
||
│ │ └── almanac.spec.ts
|
||
│ └── integration/ # 集成测试
|
||
│ └── cross-module.spec.ts
|
||
│
|
||
├── api/ # API测试层(Python pytest)
|
||
│ ├── core/ # 核心模块
|
||
│ │ ├── config_manager.py # 配置管理
|
||
│ │ ├── logger_manager.py # 日志管理
|
||
│ │ ├── test_engine.py # 测试引擎
|
||
│ │ └── validation_engine.py # 验证引擎
|
||
│ ├── helpers/ # 辅助工具
|
||
│ │ ├── api_client.py # API客户端
|
||
│ │ ├── auth_manager.py # 认证管理
|
||
│ │ └── data_factory.py # 数据工厂
|
||
│ ├── models/ # 数据模型
|
||
│ │ ├── test_models.py # 测试模型
|
||
│ │ └── exceptions.py # 异常定义
|
||
│ ├── orchestrator/ # 测试编排
|
||
│ │ └── test_orchestrator.py # 测试编排器
|
||
│ ├── report/ # 报告生成
|
||
│ │ └── report_manager.py # 报告管理器
|
||
│ └── tests/ # API测试用例
|
||
│ ├── unit/ # 单元测试
|
||
│ │ ├── test_config_manager.py
|
||
│ │ ├── test_api_client.py
|
||
│ │ └── test_data_factory.py
|
||
│ ├── integration/ # 集成测试
|
||
│ │ ├── test_user_api.py
|
||
│ │ ├── test_role_api.py
|
||
│ │ └── test_menu_api.py
|
||
│ └── e2e/ # E2E API测试
|
||
│ └── test_complete_flow.py
|
||
│
|
||
├── unit/ # 单元测试层
|
||
│ ├── admin/ # 前端单元测试(Vitest)
|
||
│ │ ├── services/
|
||
│ │ │ ├── auth.service.test.ts
|
||
│ │ │ ├── user.service.test.ts
|
||
│ │ │ └── role.service.test.ts
|
||
│ │ ├── stores/
|
||
│ │ │ ├── auth.store.test.ts
|
||
│ │ │ └── user.store.test.ts
|
||
│ │ └── components/
|
||
│ │ └── *.test.ts
|
||
│ ├── uniapp/ # UniApp单元测试(Vitest)
|
||
│ │ └── services/
|
||
│ │ ├── calendarService.test.ts
|
||
│ │ └── cacheService.test.ts
|
||
│ └── backend/ # 后端单元测试(JUnit)
|
||
│ └── [保留在各模块的src/test/java/目录]
|
||
│
|
||
├── config/ # 统一配置
|
||
│ ├── playwright.config.ts # Playwright配置
|
||
│ ├── vitest.config.ts # Vitest配置
|
||
│ ├── pytest.ini # pytest配置
|
||
│ └── test-config.yml # 测试配置
|
||
│
|
||
├── scripts/ # 测试脚本
|
||
│ ├── run-all-tests.sh # 运行所有测试
|
||
│ ├── run-e2e-tests.sh # 运行E2E测试
|
||
│ ├── run-api-tests.sh # 运行API测试
|
||
│ ├── run-unit-tests.sh # 运行单元测试
|
||
│ ├── cleanup.sh # 清理测试环境
|
||
│ ├── generate-report.sh # 生成测试报告
|
||
│ └── setup-test-env.sh # 设置测试环境
|
||
│
|
||
├── docs/ # 测试文档
|
||
│ ├── README.md # 使用指南
|
||
│ ├── ARCHITECTURE.md # 架构设计
|
||
│ ├── API.md # API文档
|
||
│ └── BEST_PRACTICES.md # 最佳实践
|
||
│
|
||
├── package.json # Node.js依赖
|
||
├── pyproject.toml # Python依赖
|
||
├── tsconfig.json # TypeScript配置
|
||
└── .env.example # 环境变量示例
|
||
```
|
||
|
||
### 2. 核心模块设计
|
||
|
||
#### 2.1 配置管理(test-config.ts)
|
||
|
||
```typescript
|
||
export interface TestEnvironment {
|
||
name: string;
|
||
baseURL: string;
|
||
apiBaseURL: string;
|
||
uniappBaseURL: string;
|
||
mockEnabled: boolean;
|
||
timeout: number;
|
||
credentials: {
|
||
username: string;
|
||
password: string;
|
||
};
|
||
}
|
||
|
||
export class TestConfig {
|
||
private static instance: TestConfig;
|
||
private currentEnv: TestEnvironment;
|
||
|
||
private constructor() {
|
||
this.currentEnv = this.loadEnvironment();
|
||
}
|
||
|
||
static getInstance(): TestConfig {
|
||
if (!TestConfig.instance) {
|
||
TestConfig.instance = new TestConfig();
|
||
}
|
||
return TestConfig.instance;
|
||
}
|
||
|
||
getEnvironment(): TestEnvironment {
|
||
return this.currentEnv;
|
||
}
|
||
|
||
setEnvironment(envName: string): void {
|
||
this.currentEnv = this.loadEnvironment(envName);
|
||
}
|
||
|
||
private loadEnvironment(envName?: string): TestEnvironment {
|
||
const name = envName || process.env.TEST_ENV || 'local';
|
||
|
||
return {
|
||
name,
|
||
baseURL: process.env.ADMIN_BASE_URL || 'http://localhost:5174',
|
||
apiBaseURL: process.env.API_BASE_URL || 'http://127.0.0.1:8080',
|
||
uniappBaseURL: process.env.UNIAPP_BASE_URL || 'http://localhost:8081',
|
||
mockEnabled: process.env.MOCK_ENABLED === 'true',
|
||
timeout: parseInt(process.env.TEST_TIMEOUT || '30000'),
|
||
credentials: {
|
||
username: process.env.TEST_USERNAME || 'admin',
|
||
password: process.env.TEST_PASSWORD || 'admin123'
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
export const testConfig = TestConfig.getInstance();
|
||
```
|
||
|
||
#### 2.2 日志管理(test-logger.ts)
|
||
|
||
```typescript
|
||
export enum LogLevel {
|
||
DEBUG = 'DEBUG',
|
||
INFO = 'INFO',
|
||
WARN = 'WARN',
|
||
ERROR = 'ERROR'
|
||
}
|
||
|
||
export class TestLogger {
|
||
private static instance: TestLogger;
|
||
private logs: Array<{ level: LogLevel; message: string; timestamp: Date }> = [];
|
||
|
||
private constructor() {}
|
||
|
||
static getInstance(): TestLogger {
|
||
if (!TestLogger.instance) {
|
||
TestLogger.instance = new TestLogger();
|
||
}
|
||
return TestLogger.instance;
|
||
}
|
||
|
||
debug(message: string): void {
|
||
this.log(LogLevel.DEBUG, message);
|
||
}
|
||
|
||
info(message: string): void {
|
||
this.log(LogLevel.INFO, message);
|
||
}
|
||
|
||
warn(message: string): void {
|
||
this.log(LogLevel.WARN, message);
|
||
}
|
||
|
||
error(message: string, error?: Error): void {
|
||
this.log(LogLevel.ERROR, message);
|
||
if (error) {
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
startTest(testName: string): void {
|
||
this.info(`\n========== 开始测试: ${testName} ==========`);
|
||
}
|
||
|
||
endTest(testName: string, status: string): void {
|
||
this.info(`========== 结束测试: ${testName} (${status}) ==========`);
|
||
}
|
||
|
||
startStep(stepName: string): void {
|
||
this.info(` [步骤] ${stepName}`);
|
||
}
|
||
|
||
endStep(stepName: string, status: string): void {
|
||
this.info(` [步骤] ${stepName} (${status})`);
|
||
}
|
||
|
||
private log(level: LogLevel, message: string): void {
|
||
const timestamp = new Date();
|
||
this.logs.push({ level, message, timestamp });
|
||
|
||
const logMessage = `[${timestamp.toISOString()}] [${level}] ${message}`;
|
||
console.log(logMessage);
|
||
}
|
||
|
||
getLogs(): Array<{ level: LogLevel; message: string; timestamp: Date }> {
|
||
return this.logs;
|
||
}
|
||
|
||
clearLogs(): void {
|
||
this.logs = [];
|
||
}
|
||
}
|
||
|
||
export const testLogger = TestLogger.getInstance();
|
||
```
|
||
|
||
#### 2.3 数据管理(test-data-manager.ts)
|
||
|
||
```typescript
|
||
export interface TestUser {
|
||
id?: number;
|
||
username: string;
|
||
password: string;
|
||
realName: string;
|
||
email: string;
|
||
phone?: string;
|
||
}
|
||
|
||
export interface TestRole {
|
||
id?: number;
|
||
roleName: string;
|
||
roleCode: string;
|
||
description?: string;
|
||
}
|
||
|
||
export class TestDataManager {
|
||
private static instance: TestDataManager;
|
||
private testData: Map<string, any> = new Map();
|
||
|
||
private constructor() {}
|
||
|
||
static getInstance(): TestDataManager {
|
||
if (!TestDataManager.instance) {
|
||
TestDataManager.instance = new TestDataManager();
|
||
}
|
||
return TestDataManager.instance;
|
||
}
|
||
|
||
async createTestUser(userData: Partial<TestUser>): Promise<TestUser> {
|
||
const user: TestUser = {
|
||
username: `test_${Date.now()}`,
|
||
password: 'Test123456',
|
||
realName: '测试用户',
|
||
email: `test_${Date.now()}@example.com`,
|
||
...userData
|
||
};
|
||
|
||
this.testData.set(`user_${user.username}`, user);
|
||
return user;
|
||
}
|
||
|
||
async createTestRole(roleData: Partial<TestRole>): Promise<TestRole> {
|
||
const role: TestRole = {
|
||
roleName: `测试角色_${Date.now()}`,
|
||
roleCode: `TEST_ROLE_${Date.now()}`,
|
||
...roleData
|
||
};
|
||
|
||
this.testData.set(`role_${role.roleCode}`, role);
|
||
return role;
|
||
}
|
||
|
||
getTestData(key: string): any {
|
||
return this.testData.get(key);
|
||
}
|
||
|
||
async cleanup(): Promise<void> {
|
||
this.testData.clear();
|
||
}
|
||
}
|
||
|
||
export const testDataManager = TestDataManager.getInstance();
|
||
```
|
||
|
||
### 3. 测试辅助工具设计
|
||
|
||
#### 3.1 表单辅助(form-helper.ts)
|
||
|
||
```typescript
|
||
import { Page, Locator } from '@playwright/test';
|
||
|
||
export class FormHelper {
|
||
constructor(private page: Page) {}
|
||
|
||
async fillField(selector: string, value: string): Promise<void> {
|
||
await this.page.fill(selector, value);
|
||
}
|
||
|
||
async fillForm(fields: Record<string, { value: string; timeout?: number }>): Promise<void> {
|
||
for (const [selector, config] of Object.entries(fields)) {
|
||
await this.page.fill(selector, config.value);
|
||
}
|
||
}
|
||
|
||
async selectOption(selector: string, value: string): Promise<void> {
|
||
await this.page.selectOption(selector, value);
|
||
}
|
||
|
||
async checkCheckbox(selector: string): Promise<void> {
|
||
await this.page.check(selector);
|
||
}
|
||
|
||
async uncheckCheckbox(selector: string): Promise<void> {
|
||
await this.page.uncheck(selector);
|
||
}
|
||
|
||
async submitForm(selector?: string): Promise<void> {
|
||
if (selector) {
|
||
await this.page.click(selector);
|
||
} else {
|
||
await this.page.keyboard.press('Enter');
|
||
}
|
||
}
|
||
|
||
async clearField(selector: string): Promise<void> {
|
||
await this.page.fill(selector, '');
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3.2 表格辅助(table-helper.ts)
|
||
|
||
```typescript
|
||
import { Page } from '@playwright/test';
|
||
|
||
export class TableHelper {
|
||
constructor(private page: Page) {}
|
||
|
||
async getRowCount(tableSelector: string): Promise<number> {
|
||
const rows = await this.page.locator(`${tableSelector} tbody tr`).count();
|
||
return rows;
|
||
}
|
||
|
||
async getCellText(tableSelector: string, row: number, col: number): Promise<string> {
|
||
const cell = await this.page.locator(
|
||
`${tableSelector} tbody tr:nth-child(${row}) td:nth-child(${col})`
|
||
);
|
||
return await cell.textContent() || '';
|
||
}
|
||
|
||
async findRowsByCellText(tableSelector: string, searchText: string): Promise<number[]> {
|
||
const rows: number[] = [];
|
||
const rowCount = await this.getRowCount(tableSelector);
|
||
|
||
for (let i = 1; i <= rowCount; i++) {
|
||
const rowText = await this.page.locator(
|
||
`${tableSelector} tbody tr:nth-child(${i})`
|
||
).textContent();
|
||
|
||
if (rowText?.includes(searchText)) {
|
||
rows.push(i);
|
||
}
|
||
}
|
||
|
||
return rows;
|
||
}
|
||
|
||
async clickRow(tableSelector: string, row: number): Promise<void> {
|
||
await this.page.click(`${tableSelector} tbody tr:nth-child(${row})`);
|
||
}
|
||
|
||
async clickCell(tableSelector: string, row: number, col: number): Promise<void> {
|
||
await this.page.click(
|
||
`${tableSelector} tbody tr:nth-child(${row}) td:nth-child(${col})`
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 统一测试执行流程
|
||
|
||
#### 4.1 package.json 脚本
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"test": "npm run test:all",
|
||
"test:all": "npm run test:unit && npm run test:api && npm run test:e2e",
|
||
"test:unit": "npm run test:unit:admin && npm run test:unit:uniapp",
|
||
"test:unit:admin": "cd ../everything-is-suitable-admin && npm run test",
|
||
"test:unit:uniapp": "cd ../everything-is-suitable-uniapp && npm run test",
|
||
"test:api": "cd api && pytest tests/ -v",
|
||
"test:e2e": "playwright test",
|
||
"test:e2e:admin": "playwright test e2e/tests/admin/",
|
||
"test:e2e:uniapp": "playwright test e2e/tests/uniapp/",
|
||
"test:e2e:headed": "playwright test --headed",
|
||
"test:e2e:debug": "playwright test --debug",
|
||
"test:e2e:ui": "playwright test --ui",
|
||
"test:report": "playwright show-report",
|
||
"test:cleanup": "./scripts/cleanup.sh",
|
||
"test:setup": "./scripts/setup-test-env.sh"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4.2 统一配置文件(playwright.config.ts)
|
||
|
||
```typescript
|
||
import { defineConfig, devices } from '@playwright/test';
|
||
|
||
export default defineConfig({
|
||
testDir: './e2e/tests',
|
||
fullyParallel: true,
|
||
forbidOnly: !!process.env.CI,
|
||
retries: process.env.CI ? 2 : 0,
|
||
workers: process.env.CI ? 2 : 4,
|
||
reporter: [
|
||
['html'],
|
||
['json', { outputFile: 'test-results/results.json' }],
|
||
['junit', { outputFile: 'test-results/junit.xml' }]
|
||
],
|
||
use: {
|
||
baseURL: process.env.ADMIN_BASE_URL || 'http://localhost:5174',
|
||
trace: 'on-first-retry',
|
||
screenshot: 'only-on-failure',
|
||
video: 'retain-on-failure',
|
||
actionTimeout: 30000,
|
||
navigationTimeout: 30000
|
||
},
|
||
projects: [
|
||
{
|
||
name: 'chromium',
|
||
use: { ...devices['Desktop Chrome'] },
|
||
},
|
||
{
|
||
name: 'firefox',
|
||
use: { ...devices['Desktop Firefox'] },
|
||
},
|
||
{
|
||
name: 'webkit',
|
||
use: { ...devices['Desktop Safari'] },
|
||
},
|
||
{
|
||
name: 'Mobile Chrome',
|
||
use: { ...devices['Pixel 5'] },
|
||
},
|
||
{
|
||
name: 'Mobile Safari',
|
||
use: { ...devices['iPhone 12'] },
|
||
},
|
||
],
|
||
webServer: {
|
||
command: 'npm run dev',
|
||
url: 'http://localhost:5174',
|
||
reuseExistingServer: !process.env.CI,
|
||
timeout: 120000,
|
||
},
|
||
});
|
||
```
|
||
|
||
### 5. 测试报告统一
|
||
|
||
#### 5.1 报告生成器(test-reporter.ts)
|
||
|
||
```typescript
|
||
export interface TestResult {
|
||
testName: string;
|
||
status: 'passed' | 'failed' | 'skipped';
|
||
duration: number;
|
||
error?: string;
|
||
screenshot?: string;
|
||
}
|
||
|
||
export class TestReporter {
|
||
private results: TestResult[] = [];
|
||
|
||
addResult(result: TestResult): void {
|
||
this.results.push(result);
|
||
}
|
||
|
||
generateJSON(): string {
|
||
return JSON.stringify({
|
||
timestamp: new Date().toISOString(),
|
||
total: this.results.length,
|
||
passed: this.results.filter(r => r.status === 'passed').length,
|
||
failed: this.results.filter(r => r.status === 'failed').length,
|
||
skipped: this.results.filter(r => r.status === 'skipped').length,
|
||
results: this.results
|
||
}, null, 2);
|
||
}
|
||
|
||
generateHTML(): string {
|
||
const passed = this.results.filter(r => r.status === 'passed').length;
|
||
const failed = this.results.filter(r => r.status === 'failed').length;
|
||
const skipped = this.results.filter(r => r.status === 'skipped').length;
|
||
|
||
return `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>测试报告</title>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||
.summary { background: #f5f5f5; padding: 20px; margin-bottom: 20px; }
|
||
.passed { color: green; }
|
||
.failed { color: red; }
|
||
.skipped { color: orange; }
|
||
table { border-collapse: collapse; width: 100%; }
|
||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||
th { background-color: #4CAF50; color: white; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>测试报告</h1>
|
||
<div class="summary">
|
||
<p>总测试数: ${this.results.length}</p>
|
||
<p class="passed">通过: ${passed}</p>
|
||
<p class="failed">失败: ${failed}</p>
|
||
<p class="skipped">跳过: ${skipped}</p>
|
||
</div>
|
||
<table>
|
||
<tr>
|
||
<th>测试名称</th>
|
||
<th>状态</th>
|
||
<th>耗时</th>
|
||
<th>错误</th>
|
||
</tr>
|
||
${this.results.map(r => `
|
||
<tr>
|
||
<td>${r.testName}</td>
|
||
<td class="${r.status}">${r.status}</td>
|
||
<td>${r.duration}ms</td>
|
||
<td>${r.error || ''}</td>
|
||
</tr>
|
||
`).join('')}
|
||
</table>
|
||
</body>
|
||
</html>
|
||
`;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6. 环境变量统一
|
||
|
||
#### 6.1 .env.example
|
||
|
||
```env
|
||
# 测试环境配置
|
||
TEST_ENV=local
|
||
|
||
# 服务地址
|
||
ADMIN_BASE_URL=http://localhost:5174
|
||
UNIAPP_BASE_URL=http://localhost:8081
|
||
API_BASE_URL=http://127.0.0.1:8080
|
||
|
||
# 测试账号
|
||
TEST_USERNAME=admin
|
||
TEST_PASSWORD=admin123
|
||
|
||
# Mock配置
|
||
MOCK_ENABLED=false
|
||
|
||
# 超时配置
|
||
TEST_TIMEOUT=30000
|
||
|
||
# 数据库配置
|
||
DB_HOST=localhost
|
||
DB_PORT=3306
|
||
DB_NAME=test_db
|
||
DB_USERNAME=root
|
||
DB_PASSWORD=root
|
||
|
||
# 报告配置
|
||
REPORT_DIR=test-results
|
||
REPORT_FORMAT=json,html,junit
|
||
```
|
||
|
||
## 实施计划
|
||
|
||
### 阶段1:清理过时文件(1-2天)
|
||
1. 删除根目录过时测试脚本
|
||
2. 删除根目录过时测试报告
|
||
3. 删除 .trae/docs/ 过时文档
|
||
4. 删除 docs/plans/ 过时测试计划
|
||
5. 删除子项目过时文档
|
||
|
||
### 阶段2:统一测试框架(3-5天)
|
||
1. 创建统一的核心模块
|
||
2. 合并重复的helper类
|
||
3. 创建统一的配置管理
|
||
4. 创建统一的数据管理器
|
||
|
||
### 阶段3:优化测试配置(2-3天)
|
||
1. 合并Playwright配置
|
||
2. 统一环境变量配置
|
||
3. 优化测试超时设置
|
||
4. 配置并行执行
|
||
|
||
### 阶段4:生成新文档(2-3天)
|
||
1. 生成使用指南
|
||
2. 生成API文档
|
||
3. 生成架构文档
|
||
4. 生成最佳实践文档
|
||
|
||
### 阶段5:验证和优化(2-3天)
|
||
1. 运行所有测试
|
||
2. 验证测试覆盖率
|
||
3. 优化测试性能
|
||
4. 更新CI/CD配置
|
||
|
||
## 预期收益
|
||
|
||
1. **代码复用性提升60%+**:消除重复代码,统一测试工具类
|
||
2. **测试效率提升40%+**:统一测试执行入口,优化测试流程
|
||
3. **维护成本降低50%+**:统一配置管理,减少维护工作量
|
||
4. **文档质量提升**:删除过时文档,生成最新文档
|
||
|
||
## 风险评估
|
||
|
||
1. **高风险**:删除过时文件可能影响历史追溯
|
||
- 缓解措施:使用Git分支,保留备份
|
||
|
||
2. **中风险**:合并配置可能破坏现有测试
|
||
- 缓解措施:分阶段实施,充分测试
|
||
|
||
3. **低风险**:统一框架可能需要大量代码重构
|
||
- 缓解措施:逐步重构,保持向后兼容
|
||
|
||
---
|
||
|
||
**设计时间**: 2026-03-06
|
||
**设计人员**: 张翔(资深金融级高级自动化测试工程师)
|
||
**版本**: v1.0
|