# 统一测试框架架构设计 ## 设计目标 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 = new Map(); private constructor() {} static getInstance(): TestDataManager { if (!TestDataManager.instance) { TestDataManager.instance = new TestDataManager(); } return TestDataManager.instance; } async createTestUser(userData: Partial): Promise { 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): Promise { 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 { 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 { await this.page.fill(selector, value); } async fillForm(fields: Record): Promise { for (const [selector, config] of Object.entries(fields)) { await this.page.fill(selector, config.value); } } async selectOption(selector: string, value: string): Promise { await this.page.selectOption(selector, value); } async checkCheckbox(selector: string): Promise { await this.page.check(selector); } async uncheckCheckbox(selector: string): Promise { await this.page.uncheck(selector); } async submitForm(selector?: string): Promise { if (selector) { await this.page.click(selector); } else { await this.page.keyboard.press('Enter'); } } async clearField(selector: string): Promise { 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 { const rows = await this.page.locator(`${tableSelector} tbody tr`).count(); return rows; } async getCellText(tableSelector: string, row: number, col: number): Promise { 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 { 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 { await this.page.click(`${tableSelector} tbody tr:nth-child(${row})`); } async clickCell(tableSelector: string, row: number, col: number): Promise { 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 ` 测试报告

测试报告

总测试数: ${this.results.length}

通过: ${passed}

失败: ${failed}

跳过: ${skipped}

${this.results.map(r => ` `).join('')}
测试名称 状态 耗时 错误
${r.testName} ${r.status} ${r.duration}ms ${r.error || ''}
`; } } ``` ### 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