08ea5fbe98
添加用户管理视图、API和状态管理文件
1267 lines
32 KiB
Markdown
1267 lines
32 KiB
Markdown
# 测试套件修复计划
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** 修复测试套件中的关键问题,使整体测试通过率达到95%以上,E2E测试可正常运行,符合金融级测试标准。
|
|
|
|
**Architecture:** 采用分层修复策略,从基础设施配置开始,逐步修复依赖管理和测试隔离问题,最后优化测试覆盖率和稳定性。
|
|
|
|
**Tech Stack:** Playwright (E2E), Vitest (前端单元测试), pytest (API测试), TypeScript, Python
|
|
|
|
---
|
|
|
|
## 执行策略
|
|
|
|
本计划按照优先级分为三个阶段:
|
|
|
|
- **P0阶段**:修复阻塞测试运行的基础设施问题(预计2-3小时)
|
|
- **P1阶段**:提升测试稳定性和通过率(预计4-6小时)
|
|
- **P2阶段**:优化测试架构和覆盖率(预计8-12小时)
|
|
|
|
每个任务都遵循TDD原则:先写失败测试,再实现最小化修复,最后验证通过。
|
|
|
|
---
|
|
|
|
## P0阶段:基础设施修复(立即执行)
|
|
|
|
### Task 1: 创建Playwright配置文件
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin/playwright.config.ts`
|
|
|
|
**Step 1: 创建配置文件基础结构**
|
|
|
|
```typescript
|
|
import { defineConfig, devices } from '@playwright/test';
|
|
|
|
export default defineConfig({
|
|
testDir: './e2e',
|
|
fullyParallel: true,
|
|
forbidOnly: !!process.env.CI,
|
|
retries: process.env.CI ? 2 : 0,
|
|
workers: process.env.CI ? 1 : undefined,
|
|
reporter: [
|
|
['html'],
|
|
['list'],
|
|
['json', { outputFile: 'test-results/results.json' }]
|
|
],
|
|
use: {
|
|
baseURL: process.env.E2E_BASE_URL || 'http://localhost:5174',
|
|
trace: 'on-first-retry',
|
|
screenshot: 'only-on-failure',
|
|
video: 'retain-on-failure',
|
|
},
|
|
projects: [
|
|
{
|
|
name: 'chromium',
|
|
use: { ...devices['Desktop Chrome'] },
|
|
},
|
|
{
|
|
name: 'firefox',
|
|
use: { ...devices['Desktop Firefox'] },
|
|
},
|
|
{
|
|
name: 'webkit',
|
|
use: { ...devices['Desktop Safari'] },
|
|
},
|
|
],
|
|
webServer: {
|
|
command: 'npm run dev',
|
|
url: 'http://localhost:5174',
|
|
reuseExistingServer: !process.env.CI,
|
|
timeout: 120000,
|
|
},
|
|
});
|
|
```
|
|
|
|
**Step 2: 验证配置文件语法**
|
|
|
|
Run: `cd everything-is-suitable-admin && npx playwright test --config=playwright.config.ts --dry-run`
|
|
|
|
Expected: 配置文件语法正确,无错误输出
|
|
|
|
**Step 3: 运行单个E2E测试验证配置**
|
|
|
|
Run: `cd everything-is-suitable-admin && npx playwright test e2e/auth.spec.ts:89 --reporter=list`
|
|
|
|
Expected: 测试开始执行(可能失败,但配置生效)
|
|
|
|
**Step 4: 提交配置文件**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/playwright.config.ts
|
|
git commit -m "feat: add Playwright configuration for E2E tests"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: 修复E2E测试中的相对路径问题
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/e2e/auth.spec.ts:86`
|
|
- Modify: `everything-is-suitable-admin/e2e/user.spec.ts`
|
|
- Modify: `everything-is-suitable-admin/e2e/role.spec.ts`
|
|
- Modify: `everything-is-suitable-admin/e2e/menu.spec.ts`
|
|
|
|
**Step 1: 修改auth.spec.ts中的导航路径**
|
|
|
|
查找所有 `page.goto('/login')` 并修改为:
|
|
|
|
```typescript
|
|
// 修改前
|
|
await page.goto('/login');
|
|
|
|
// 修改后
|
|
await page.goto('/login');
|
|
```
|
|
|
|
注意:由于配置了baseURL,相对路径应该可以工作。如果仍有问题,使用绝对路径:
|
|
|
|
```typescript
|
|
await page.goto('http://localhost:5174/login');
|
|
```
|
|
|
|
**Step 2: 检查其他测试文件的路径**
|
|
|
|
Run: `cd everything-is-suitable-admin && grep -n "page.goto" e2e/*.spec.ts`
|
|
|
|
Expected: 列出所有page.goto调用
|
|
|
|
**Step 3: 统一修改所有测试文件的路径**
|
|
|
|
根据grep结果,逐个文件修改路径问题。
|
|
|
|
**Step 4: 运行E2E测试验证修复**
|
|
|
|
Run: `cd everything-is-suitable-admin && npx playwright test e2e/auth.spec.ts --reporter=list`
|
|
|
|
Expected: 至少第一个测试应该能够加载登录页面
|
|
|
|
**Step 5: 提交修复**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/e2e/
|
|
git commit -m "fix: resolve navigation path issues in E2E tests"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: 创建缺失的前端测试工具模块
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin/src/utils/formRules.ts`
|
|
- Create: `everything-is-suitable-admin/src/utils/formValidator.ts`
|
|
- Create: `everything-is-suitable-admin/src/utils/passwordValidator.ts`
|
|
|
|
**Step 1: 创建formRules.ts**
|
|
|
|
```typescript
|
|
import { Rule } from 'ant-design-vue/es/form';
|
|
|
|
export const formRules: Record<string, Rule[]> = {
|
|
username: [
|
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
|
{ min: 3, max: 20, message: '用户名长度在3到20个字符', trigger: 'blur' },
|
|
],
|
|
password: [
|
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
|
{ min: 6, message: '密码长度不能少于6个字符', trigger: 'blur' },
|
|
],
|
|
email: [
|
|
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
|
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' },
|
|
],
|
|
phone: [
|
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号', trigger: 'blur' },
|
|
],
|
|
};
|
|
|
|
export default formRules;
|
|
```
|
|
|
|
**Step 2: 创建formValidator.ts**
|
|
|
|
```typescript
|
|
export interface ValidationResult {
|
|
valid: boolean;
|
|
message?: string;
|
|
}
|
|
|
|
export const formValidator = {
|
|
username: (value: string): ValidationResult => {
|
|
if (!value) {
|
|
return { valid: false, message: '用户名不能为空' };
|
|
}
|
|
if (value.length < 3 || value.length > 20) {
|
|
return { valid: false, message: '用户名长度在3到20个字符' };
|
|
}
|
|
return { valid: true };
|
|
},
|
|
|
|
email: (value: string): ValidationResult => {
|
|
if (!value) {
|
|
return { valid: false, message: '邮箱不能为空' };
|
|
}
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(value)) {
|
|
return { valid: false, message: '请输入有效的邮箱地址' };
|
|
}
|
|
return { valid: true };
|
|
},
|
|
|
|
phone: (value: string): ValidationResult => {
|
|
if (!value) {
|
|
return { valid: false, message: '手机号不能为空' };
|
|
}
|
|
const phoneRegex = /^1[3-9]\d{9}$/;
|
|
if (!phoneRegex.test(value)) {
|
|
return { valid: false, message: '请输入有效的手机号' };
|
|
}
|
|
return { valid: true };
|
|
},
|
|
|
|
required: (value: any, fieldName: string = '字段'): ValidationResult => {
|
|
if (value === null || value === undefined || value === '') {
|
|
return { valid: false, message: `${fieldName}不能为空` };
|
|
}
|
|
return { valid: true };
|
|
},
|
|
};
|
|
|
|
export default formValidator;
|
|
```
|
|
|
|
**Step 3: 创建passwordValidator.ts**
|
|
|
|
```typescript
|
|
export interface PasswordValidationResult {
|
|
valid: boolean;
|
|
score: number;
|
|
message?: string;
|
|
suggestions?: string[];
|
|
}
|
|
|
|
export const passwordValidator = {
|
|
validate: (password: string): PasswordValidationResult => {
|
|
if (!password) {
|
|
return {
|
|
valid: false,
|
|
score: 0,
|
|
message: '密码不能为空',
|
|
};
|
|
}
|
|
|
|
let score = 0;
|
|
const suggestions: string[] = [];
|
|
|
|
if (password.length >= 8) score += 1;
|
|
else suggestions.push('密码长度至少8个字符');
|
|
|
|
if (password.length >= 12) score += 1;
|
|
else suggestions.push('建议使用12个字符以上的密码');
|
|
|
|
if (/[a-z]/.test(password)) score += 1;
|
|
else suggestions.push('添加小写字母');
|
|
|
|
if (/[A-Z]/.test(password)) score += 1;
|
|
else suggestions.push('添加大写字母');
|
|
|
|
if (/[0-9]/.test(password)) score += 1;
|
|
else suggestions.push('添加数字');
|
|
|
|
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
|
|
else suggestions.push('添加特殊字符');
|
|
|
|
const valid = score >= 4 && password.length >= 8;
|
|
|
|
return {
|
|
valid,
|
|
score,
|
|
message: valid ? '密码强度良好' : '密码强度不足',
|
|
suggestions: valid ? [] : suggestions,
|
|
};
|
|
},
|
|
|
|
getStrengthLabel: (score: number): string => {
|
|
if (score <= 2) return '弱';
|
|
if (score <= 4) return '中';
|
|
if (score <= 5) return '强';
|
|
return '非常强';
|
|
},
|
|
|
|
getStrengthColor: (score: number): string => {
|
|
if (score <= 2) return '#ff4d4f';
|
|
if (score <= 4) return '#faad14';
|
|
if (score <= 5) return '#52c41a';
|
|
return '#1890ff';
|
|
},
|
|
};
|
|
|
|
export default passwordValidator;
|
|
```
|
|
|
|
**Step 4: 验证模块导入**
|
|
|
|
Run: `cd everything-is-suitable-admin && npm run test -- src/utils/__tests__/formRules.test.ts`
|
|
|
|
Expected: 测试能够运行(可能失败,但模块可以导入)
|
|
|
|
**Step 5: 提交新模块**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/src/utils/formRules.ts
|
|
git add everything-is-suitable-admin/src/utils/formValidator.ts
|
|
git add everything-is-suitable-admin/src/utils/passwordValidator.ts
|
|
git commit -m "feat: add missing form validation utility modules"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: 修复dayjs本地化模块导入
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/src/utils/date.ts`
|
|
|
|
**Step 1: 检查当前导入语句**
|
|
|
|
Run: `cd everything-is-suitable-admin && grep -n "dayjs/locale" src/utils/date.ts`
|
|
|
|
Expected: 找到问题导入语句
|
|
|
|
**Step 2: 修改导入语句**
|
|
|
|
```typescript
|
|
// 修改前
|
|
import 'dayjs/locale/zh-cn';
|
|
|
|
// 修改后
|
|
import 'dayjs/locale/zh-cn.js';
|
|
```
|
|
|
|
或者使用正确的包名:
|
|
|
|
```typescript
|
|
import dayjs from 'dayjs';
|
|
import 'dayjs/locale/zh-cn';
|
|
|
|
dayjs.locale('zh-cn');
|
|
```
|
|
|
|
**Step 3: 验证修复**
|
|
|
|
Run: `cd everything-is-suitable-admin && npm run test -- src/utils/date.test.ts`
|
|
|
|
Expected: 测试通过,无模块导入错误
|
|
|
|
**Step 4: 提交修复**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/src/utils/date.ts
|
|
git commit -m "fix: resolve dayjs locale import issue"
|
|
```
|
|
|
|
---
|
|
|
|
## P1阶段:测试稳定性提升(本周执行)
|
|
|
|
### Task 5: 实现测试数据清理机制
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin/src/test/test-setup.ts`
|
|
- Create: `everything-is-suitable-admin/src/test/test-cleanup.ts`
|
|
|
|
**Step 1: 创建测试数据清理工具**
|
|
|
|
```typescript
|
|
import { cleanup } from '@testing-library/vue';
|
|
|
|
export const testDataCleanup = {
|
|
cleanupTestData: async () => {
|
|
cleanup();
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
},
|
|
|
|
generateUniqueUsername: (prefix: string = 'test'): string => {
|
|
const timestamp = Date.now();
|
|
const random = Math.floor(Math.random() * 1000);
|
|
return `${prefix}_${timestamp}_${random}`;
|
|
},
|
|
|
|
generateUniqueEmail: (prefix: string = 'test'): string => {
|
|
const timestamp = Date.now();
|
|
const random = Math.floor(Math.random() * 1000);
|
|
return `${prefix}_${timestamp}_${random}@example.com`;
|
|
},
|
|
};
|
|
|
|
export default testDataCleanup;
|
|
```
|
|
|
|
**Step 2: 修改测试文件使用唯一数据**
|
|
|
|
查找所有硬编码的用户名和邮箱:
|
|
|
|
Run: `cd everything-is-suitable-admin && grep -rn "testuser\|test@example.com" src/test/`
|
|
|
|
Expected: 列出所有硬编码的测试数据
|
|
|
|
**Step 3: 替换硬编码数据为唯一生成**
|
|
|
|
在每个测试文件中导入并使用:
|
|
|
|
```typescript
|
|
import { testDataCleanup } from '@/test/test-cleanup';
|
|
|
|
const username = testDataCleanup.generateUniqueUsername();
|
|
const email = testDataCleanup.generateUniqueEmail();
|
|
```
|
|
|
|
**Step 4: 在vitest配置中添加全局清理**
|
|
|
|
Modify: `everything-is-suitable-admin/vitest.config.ts`
|
|
|
|
```typescript
|
|
export default defineConfig({
|
|
test: {
|
|
setupFiles: ['./src/test/setup.ts'],
|
|
teardownFiles: ['./src/test/test-cleanup.ts'],
|
|
// ... 其他配置
|
|
},
|
|
});
|
|
```
|
|
|
|
**Step 5: 验证数据隔离**
|
|
|
|
Run: `cd everything-is-suitable-admin && npm run test -- src/services/__tests__/user.service.management.test.ts`
|
|
|
|
Expected: 无重复键错误
|
|
|
|
**Step 6: 提交修复**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/src/test/
|
|
git add everything-is-suitable-admin/vitest.config.ts
|
|
git commit -m "feat: implement test data cleanup and isolation mechanism"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6: 修复Vitest与Playwright全局对象冲突
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/vitest.config.ts`
|
|
- Create: `everything-is-suitable-admin/vitest.setup.ts`
|
|
|
|
**Step 1: 创建Vitest独立设置文件**
|
|
|
|
```typescript
|
|
import { vi } from 'vitest';
|
|
|
|
// 隔离Vitest的全局对象
|
|
global.expect = vi.expect;
|
|
global.test = vi.test;
|
|
global.describe = vi.describe;
|
|
global.it = vi.it;
|
|
|
|
// 清理可能的冲突
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
```
|
|
|
|
**Step 2: 修改vitest配置使用独立设置**
|
|
|
|
```typescript
|
|
import { defineConfig } from 'vitest/config';
|
|
|
|
export default defineConfig({
|
|
test: {
|
|
setupFiles: ['./vitest.setup.ts'],
|
|
globals: true,
|
|
environment: 'jsdom',
|
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'],
|
|
exclude: ['node_modules', 'dist', 'e2e'],
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'json', 'html'],
|
|
exclude: [
|
|
'node_modules/',
|
|
'src/test/',
|
|
'**/*.d.ts',
|
|
'**/*.config.*',
|
|
'**/mockData.ts',
|
|
],
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
**Step 3: 验证配置修复**
|
|
|
|
Run: `cd everything-is-suitable-admin && npm run test -- src/test/auth.service.test.ts`
|
|
|
|
Expected: 无`$$jest-matchers-object`错误
|
|
|
|
**Step 4: 提交修复**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/vitest.config.ts
|
|
git add everything-is-suitable-admin/vitest.setup.ts
|
|
git commit -m "fix: resolve Vitest and Playwright global object conflicts"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 7: 修复Vue组件测试中的语法解析错误
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/src/test/sidebar.component.test.ts`
|
|
- Modify: `everything-is-suitable-admin/src/views/__tests__/UserManagement.management.test.ts`
|
|
|
|
**Step 1: 检查Sidebar.vue语法**
|
|
|
|
Run: `cd everything-is-suitable-admin && cat src/components/Sidebar.vue | head -20`
|
|
|
|
Expected: 确认Vue组件语法
|
|
|
|
**Step 2: 修改测试文件配置**
|
|
|
|
在测试文件中添加正确的Vue编译器配置:
|
|
|
|
```typescript
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { mount, VueWrapper } from '@vue/test-utils';
|
|
import { createPinia, setActivePinia } from 'pinia';
|
|
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
import { createI18n } from 'vue-i18n';
|
|
|
|
// 添加Vue组件编译配置
|
|
const router = createRouter({
|
|
history: createMemoryHistory(),
|
|
routes: [],
|
|
});
|
|
|
|
const i18n = createI18n({
|
|
legacy: false,
|
|
locale: 'zh-CN',
|
|
messages: {
|
|
'zh-CN': {},
|
|
},
|
|
});
|
|
|
|
describe('Sidebar Component', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia());
|
|
});
|
|
|
|
it('should render correctly', async () => {
|
|
const wrapper = mount(Sidebar, {
|
|
global: {
|
|
plugins: [router, i18n],
|
|
stubs: {
|
|
'a-layout-sider': true,
|
|
'a-menu': true,
|
|
'a-menu-item': true,
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
});
|
|
});
|
|
```
|
|
|
|
**Step 3: 验证修复**
|
|
|
|
Run: `cd everything-is-suitable-admin && npm run test -- src/test/sidebar.component.test.ts`
|
|
|
|
Expected: 无语法解析错误
|
|
|
|
**Step 4: 提交修复**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/src/test/sidebar.component.test.ts
|
|
git add everything-is-suitable-admin/src/views/__tests__/UserManagement.management.test.ts
|
|
git commit -m "fix: resolve Vue component syntax parsing errors in tests"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 8: 提升E2E测试通过率到60%+
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/e2e/auth.spec.ts`
|
|
- Modify: `everything-is-suitable-admin/e2e/mock-manager.ts`
|
|
|
|
**Step 1: 修复Mock服务配置**
|
|
|
|
确保Mock服务正确拦截API请求:
|
|
|
|
```typescript
|
|
// mock-manager.ts
|
|
export class MockManager {
|
|
private mockResponses: Map<string, any> = new Map();
|
|
|
|
addMockResponse(config: MockConfig) {
|
|
const key = `${config.method}:${config.url}`;
|
|
this.mockResponses.set(key, config.response);
|
|
}
|
|
|
|
async interceptAPIRequest(page: Page) {
|
|
await page.route('**/api/**', async (route) => {
|
|
const url = route.request().url();
|
|
const method = route.request().method();
|
|
|
|
const key = `${method}:${url}`;
|
|
if (this.mockResponses.has(key)) {
|
|
const response = this.mockResponses.get(key);
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(response),
|
|
});
|
|
} else {
|
|
await route.continue();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 修复auth.spec.ts测试用例**
|
|
|
|
确保测试等待和断言正确:
|
|
|
|
```typescript
|
|
test('应该显示登录页面', async ({ page }) => {
|
|
await expect(page).toHaveTitle(/管理系统/);
|
|
|
|
const usernameInput = page.locator('input[placeholder="请输入用户名"]');
|
|
const passwordInput = page.locator('input[placeholder="请输入密码"]');
|
|
const loginButton = page.locator('button[type="submit"]');
|
|
|
|
await expect(usernameInput).toBeVisible({ timeout: 10000 });
|
|
await expect(passwordInput).toBeVisible({ timeout: 10000 });
|
|
await expect(loginButton).toBeVisible({ timeout: 10000 });
|
|
});
|
|
```
|
|
|
|
**Step 3: 运行所有E2E测试**
|
|
|
|
Run: `cd everything-is-suitable-admin && npx playwright test --reporter=list`
|
|
|
|
Expected: 至少60%的测试通过
|
|
|
|
**Step 4: 提交修复**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/e2e/
|
|
git commit -m "fix: improve E2E test stability and pass rate to 60%+"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 9: 提升前端单元测试通过率到90%+
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/src/services/__tests__/*.test.ts`
|
|
- Modify: `everything-is-suitable-admin/src/stores/__tests__/*.test.ts`
|
|
|
|
**Step 1: 识别失败的测试**
|
|
|
|
Run: `cd everything-is-suitable-admin && npm run test 2>&1 | grep "FAIL" | head -20`
|
|
|
|
Expected: 列出所有失败的测试
|
|
|
|
**Step 2: 逐个修复失败的测试**
|
|
|
|
对于每个失败测试:
|
|
1. 查看错误信息
|
|
2. 检查测试代码
|
|
3. 修复问题(Mock、断言、异步处理等)
|
|
|
|
示例修复:
|
|
|
|
```typescript
|
|
// 修复前
|
|
it('should login successfully', async () => {
|
|
const result = await authService.login('admin', 'password');
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
// 修复后
|
|
it('should login successfully', async () => {
|
|
vi.spyOn(axios, 'post').mockResolvedValue({
|
|
data: { token: 'mock-token', userInfo: { id: 1 } }
|
|
});
|
|
|
|
const result = await authService.login('admin', 'password');
|
|
expect(result.token).toBe('mock-token');
|
|
});
|
|
```
|
|
|
|
**Step 3: 验证修复**
|
|
|
|
Run: `cd everything-is-suitable-admin && npm run test`
|
|
|
|
Expected: 至少90%的测试通过
|
|
|
|
**Step 4: 提交修复**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/src/
|
|
git commit -m "fix: improve unit test pass rate to 90%+"
|
|
```
|
|
|
|
---
|
|
|
|
## P2阶段:测试架构优化(本月执行)
|
|
|
|
### Task 10: 实现测试环境隔离
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin/src/test/test-environment.ts`
|
|
- Create: `everything-is-suitable-admin/docker-compose.test.yml`
|
|
|
|
**Step 1: 创建测试环境配置**
|
|
|
|
```typescript
|
|
export const testEnvironment = {
|
|
isTest: process.env.NODE_ENV === 'test',
|
|
isE2E: process.env.VITE_E2E_TEST === 'true',
|
|
|
|
getBaseURL: (): string => {
|
|
return process.env.E2E_BASE_URL || 'http://localhost:5174';
|
|
},
|
|
|
|
getAPIBaseURL: (): string => {
|
|
return process.env.API_BASE_URL || 'http://localhost:8080';
|
|
},
|
|
|
|
reset: async () => {
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
},
|
|
};
|
|
```
|
|
|
|
**Step 2: 创建Docker测试环境**
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
services:
|
|
test-db:
|
|
image: postgres:15
|
|
environment:
|
|
POSTGRES_DB: everything_test
|
|
POSTGRES_USER: test_user
|
|
POSTGRES_PASSWORD: test_password
|
|
ports:
|
|
- "5433:5432"
|
|
|
|
test-redis:
|
|
image: redis:7
|
|
ports:
|
|
- "6380:6379"
|
|
|
|
test-api:
|
|
build: ./everything-is-suitable-api
|
|
environment:
|
|
SPRING_PROFILES_ACTIVE: test
|
|
DB_HOST: test-db
|
|
REDIS_HOST: test-redis
|
|
depends_on:
|
|
- test-db
|
|
- test-redis
|
|
ports:
|
|
- "8081:8080"
|
|
```
|
|
|
|
**Step 3: 验证测试环境**
|
|
|
|
Run: `cd everything-is-suitable-admin && docker-compose -f docker-compose.test.yml up -d`
|
|
|
|
Expected: 测试环境启动成功
|
|
|
|
**Step 4: 提交配置**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/src/test/test-environment.ts
|
|
git add everything-is-suitable-admin/docker-compose.test.yml
|
|
git commit -m "feat: implement isolated test environment"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 11: 补充金融级测试场景
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin/e2e/financial.spec.ts`
|
|
- Create: `everything-is-suitable-admin/e2e/security.spec.ts`
|
|
- Create: `everything-is-suitable-admin/e2e/compliance.spec.ts`
|
|
|
|
**Step 1: 创建金融交易测试**
|
|
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('金融交易测试', () => {
|
|
test('应该正确处理资金转账', async ({ page }) => {
|
|
await page.goto('/transfer');
|
|
await page.fill('input[name="amount"]', '1000.00');
|
|
await page.fill('input[name="recipient"]', 'test_account');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('.success-message')).toBeVisible();
|
|
await expect(page.locator('.transaction-id')).toContainText(/TXN\d+/);
|
|
});
|
|
|
|
test('应该验证交易金额精度', async ({ page }) => {
|
|
await page.goto('/transfer');
|
|
await page.fill('input[name="amount"]', '1000.123456');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('.error-message')).toContainText('金额精度错误');
|
|
});
|
|
|
|
test('应该防止重复交易', async ({ page }) => {
|
|
const transactionId = 'TXN123456';
|
|
|
|
await page.goto('/transfer');
|
|
await page.fill('input[name="transactionId"]', transactionId);
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('.error-message')).toContainText('交易已存在');
|
|
});
|
|
});
|
|
```
|
|
|
|
**Step 2: 创建安全测试**
|
|
|
|
```typescript
|
|
test.describe('安全测试', () => {
|
|
test('应该正确处理SQL注入攻击', async ({ page }) => {
|
|
await page.goto('/login');
|
|
await page.fill('input[name="username']', "admin' OR '1'='1");
|
|
await page.fill('input[name="password"]', 'password');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('.error-message')).toContainText('登录失败');
|
|
});
|
|
|
|
test('应该正确处理XSS攻击', async ({ page }) => {
|
|
await page.goto('/profile');
|
|
await page.fill('input[name="bio"]', '<script>alert("XSS")</script>');
|
|
await page.click('button[type="submit"]');
|
|
|
|
const bio = await page.locator('.bio-display').textContent();
|
|
expect(bio).not.toContain('<script>');
|
|
});
|
|
|
|
test('应该验证API请求签名', async ({ page }) => {
|
|
await page.goto('/api/secure-data');
|
|
const response = await page.evaluate(() => fetch('/api/secure-data'));
|
|
|
|
expect(response.status).toBe(401);
|
|
});
|
|
});
|
|
```
|
|
|
|
**Step 3: 创建合规性测试**
|
|
|
|
```typescript
|
|
test.describe('合规性测试', () => {
|
|
test('应该记录所有操作日志', async ({ page }) => {
|
|
await page.goto('/dashboard');
|
|
await page.click('button[data-action="export"]');
|
|
|
|
const logs = await page.evaluate(() => {
|
|
return JSON.parse(localStorage.getItem('operationLogs') || '[]');
|
|
});
|
|
|
|
expect(logs.length).toBeGreaterThan(0);
|
|
expect(logs[0]).toHaveProperty('timestamp');
|
|
expect(logs[0]).toHaveProperty('action');
|
|
});
|
|
|
|
test('应该验证数据保留期限', async ({ page }) => {
|
|
await page.goto('/audit-logs');
|
|
await page.fill('input[name="date"]', '2020-01-01');
|
|
await page.click('button[type="search"]');
|
|
|
|
await expect(page.locator('.no-data-message')).toBeVisible();
|
|
});
|
|
|
|
test('应该保护敏感数据', async ({ page }) => {
|
|
await page.goto('/user-profile');
|
|
const passwordField = await page.locator('input[type="password"]');
|
|
const passwordValue = await passwordField.inputValue();
|
|
|
|
expect(passwordValue).toBe('');
|
|
});
|
|
});
|
|
```
|
|
|
|
**Step 4: 运行金融级测试**
|
|
|
|
Run: `cd everything-is-suitable-admin && npx playwright test e2e/financial.spec.ts e2e/security.spec.ts e2e/compliance.spec.ts --reporter=list`
|
|
|
|
Expected: 所有金融级测试通过
|
|
|
|
**Step 5: 提交测试**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/e2e/
|
|
git commit -m "feat: add financial-grade test scenarios"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 12: 实现性能测试
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-admin/e2e/performance.spec.ts`
|
|
- Create: `everything-is-suitable-admin/e2e/load-test.ts`
|
|
|
|
**Step 1: 创建性能测试**
|
|
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('性能测试', () => {
|
|
test('页面加载时间应小于2秒', async ({ page }) => {
|
|
const startTime = Date.now();
|
|
await page.goto('/dashboard');
|
|
const loadTime = Date.now() - startTime;
|
|
|
|
expect(loadTime).toBeLessThan(2000);
|
|
});
|
|
|
|
test('API响应时间应小于500ms', async ({ page }) => {
|
|
await page.goto('/users');
|
|
|
|
const responseTime = await page.evaluate(async () => {
|
|
const start = performance.now();
|
|
await fetch('/api/users');
|
|
return performance.now() - start;
|
|
});
|
|
|
|
expect(responseTime).toBeLessThan(500);
|
|
});
|
|
|
|
test('应支持1000个并发用户', async ({ browser }) => {
|
|
const context = await browser.newContext();
|
|
const pages = await Promise.all(
|
|
Array(1000).fill(0).map(() => context.newPage())
|
|
);
|
|
|
|
const results = await Promise.all(
|
|
pages.map(async (page) => {
|
|
const start = Date.now();
|
|
await page.goto('/dashboard');
|
|
return Date.now() - start;
|
|
})
|
|
);
|
|
|
|
const avgLoadTime = results.reduce((a, b) => a + b) / results.length;
|
|
expect(avgLoadTime).toBeLessThan(3000);
|
|
});
|
|
});
|
|
```
|
|
|
|
**Step 2: 创建负载测试脚本**
|
|
|
|
```typescript
|
|
import { chromium } from 'playwright';
|
|
import { performance } from 'perf_hooks';
|
|
|
|
async function runLoadTest() {
|
|
const browser = await chromium.launch();
|
|
const concurrentUsers = 100;
|
|
const duration = 60; // seconds
|
|
|
|
console.log(`Starting load test with ${concurrentUsers} concurrent users for ${duration}s`);
|
|
|
|
const startTime = performance.now();
|
|
const results = [];
|
|
|
|
for (let i = 0; i < concurrentUsers; i++) {
|
|
const context = await browser.newContext();
|
|
const page = await context.newPage();
|
|
|
|
const start = performance.now();
|
|
await page.goto('http://localhost:5174/dashboard');
|
|
const loadTime = performance.now() - start;
|
|
|
|
results.push(loadTime);
|
|
await context.close();
|
|
}
|
|
|
|
const endTime = performance.now();
|
|
const totalTime = (endTime - startTime) / 1000;
|
|
|
|
const avgLoadTime = results.reduce((a, b) => a + b) / results.length;
|
|
const maxLoadTime = Math.max(...results);
|
|
const minLoadTime = Math.min(...results);
|
|
|
|
console.log(`Load test completed in ${totalTime.toFixed(2)}s`);
|
|
console.log(`Average load time: ${avgLoadTime.toFixed(2)}ms`);
|
|
console.log(`Max load time: ${maxLoadTime.toFixed(2)}ms`);
|
|
console.log(`Min load time: ${minLoadTime.toFixed(2)}ms`);
|
|
|
|
await browser.close();
|
|
}
|
|
|
|
runLoadTest().catch(console.error);
|
|
```
|
|
|
|
**Step 3: 运行性能测试**
|
|
|
|
Run: `cd everything-is-suitable-admin && npx playwright test e2e/performance.spec.ts --reporter=list`
|
|
|
|
Expected: 所有性能测试通过
|
|
|
|
**Step 4: 运行负载测试**
|
|
|
|
Run: `cd everything-is-suitable-admin && node e2e/load-test.ts`
|
|
|
|
Expected: 负载测试完成,性能指标达标
|
|
|
|
**Step 5: 提交测试**
|
|
|
|
```bash
|
|
git add everything-is-suitable-admin/e2e/
|
|
git commit -m "feat: add performance and load testing"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 13: 建立CI/CD质量门禁
|
|
|
|
**Files:**
|
|
- Create: `.github/workflows/test-quality-gate.yml`
|
|
- Modify: `.github/workflows/coverage-check.yml`
|
|
|
|
**Step 1: 创建质量门禁工作流**
|
|
|
|
```yaml
|
|
name: Test Quality Gate
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main, develop]
|
|
|
|
jobs:
|
|
test-quality-gate:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '18'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Run API tests
|
|
run: |
|
|
cd everything-is-suitable-test/api
|
|
python -m pytest tests/unit/ --cov=apitest --cov-report=xml
|
|
continue-on-error: false
|
|
|
|
- name: Check API coverage
|
|
run: |
|
|
cd everything-is-suitable-test/api
|
|
coverage=$(python -c "import xml.etree.ElementTree as ET; tree = ET.parse('coverage.xml'); root = tree.getroot(); coverage = float(root.attrib.get('line-rate', 0)); print(f'{coverage * 100:.1f}')")
|
|
if (( $(echo "$coverage < 90" | bc -l) )); then
|
|
echo "API coverage ${coverage}% is below 90% threshold"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run unit tests
|
|
run: |
|
|
cd everything-is-suitable-admin
|
|
npm run test -- --coverage
|
|
continue-on-error: false
|
|
|
|
- name: Check unit test coverage
|
|
run: |
|
|
cd everything-is-suitable-admin
|
|
coverage=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
|
|
if (( $(echo "$coverage < 80" | bc -l) )); then
|
|
echo "Unit test coverage ${coverage}% is below 80% threshold"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run E2E tests
|
|
run: |
|
|
cd everything-is-suitable-admin
|
|
npm run test:e2e:mock
|
|
continue-on-error: false
|
|
|
|
- name: Check E2E test pass rate
|
|
run: |
|
|
cd everything-is-suitable-admin
|
|
total=$(npx playwright test --list | grep -c "test")
|
|
passed=$(npx playwright test --reporter=json | jq '.stats.expected')
|
|
pass_rate=$((passed * 100 / total))
|
|
if (( $pass_rate < 60 )); then
|
|
echo "E2E test pass rate ${pass_rate}% is below 60% threshold"
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
**Step 2: 更新覆盖率检查工作流**
|
|
|
|
```yaml
|
|
name: Coverage Check
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main, develop]
|
|
|
|
jobs:
|
|
coverage-check:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '18'
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v4
|
|
with:
|
|
python-version: '3.10'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
cd everything-is-suitable-admin
|
|
npm ci
|
|
cd ../everything-is-suitable-test/api
|
|
pip install -r requirements.txt
|
|
|
|
- name: Generate coverage reports
|
|
run: |
|
|
cd everything-is-suitable-admin
|
|
npm run test -- --coverage
|
|
cd ../everything-is-suitable-test/api
|
|
python -m pytest tests/ --cov=apitest --cov-report=xml --cov-report=html
|
|
|
|
- name: Upload coverage to Codecov
|
|
uses: codecov/codecov-action@v3
|
|
with:
|
|
files: |
|
|
everything-is-suitable-admin/coverage/coverage-final.json
|
|
everything-is-suitable-test/api/coverage.xml
|
|
flags: unittests
|
|
name: codecov-umbrella
|
|
|
|
- name: Comment PR with coverage
|
|
uses: romeovs/lcov-reporter-action@v0.3.1
|
|
with:
|
|
lcov-file: ./everything-is-suitable-admin/coverage/lcov.info
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
```
|
|
|
|
**Step 3: 验证工作流配置**
|
|
|
|
Run: `cd everything-is-suitable-admin && act -l`
|
|
|
|
Expected: 列出所有GitHub Actions工作流
|
|
|
|
**Step 4: 提交工作流**
|
|
|
|
```bash
|
|
git add .github/workflows/
|
|
git commit -m "feat: establish CI/CD quality gate with coverage thresholds"
|
|
```
|
|
|
|
---
|
|
|
|
## 验收标准
|
|
|
|
### P0阶段验收
|
|
- [x] Playwright配置文件创建完成
|
|
- [x] E2E测试能够运行(至少1个测试通过)
|
|
- [x] 前端测试依赖模块全部创建
|
|
- [x] dayjs本地化导入问题修复
|
|
|
|
### P1阶段验收
|
|
- [x] E2E测试通过率达到60%+
|
|
- [x] 前端单元测试通过率达到90%+
|
|
- [x] 测试数据冲突问题解决
|
|
- [x] Vitest与Playwright冲突解决
|
|
|
|
### P2阶段验收
|
|
- [x] 测试环境完全隔离
|
|
- [x] 金融级测试场景覆盖完整
|
|
- [x] 性能测试和负载测试实现
|
|
- [x] CI/CD质量门禁建立
|
|
|
|
### 最终验收标准
|
|
- [x] 整体测试通过率≥95%
|
|
- [x] E2E测试通过率≥60%
|
|
- [x] API测试覆盖率≥90%
|
|
- [x] 前端单元测试覆盖率≥80%
|
|
- [x] 测试执行时间≤30分钟
|
|
- [x] CI/CD质量门禁生效
|
|
|
|
---
|
|
|
|
## 风险与缓解
|
|
|
|
### 风险1: E2E测试不稳定
|
|
**缓解措施**:
|
|
- 增加重试机制
|
|
- 使用更稳定的等待策略
|
|
- 实现测试数据隔离
|
|
|
|
### 风险2: 测试执行时间过长
|
|
**缓解措施**:
|
|
- 并行执行测试
|
|
- 优化测试等待时间
|
|
- 使用Mock服务减少网络依赖
|
|
|
|
### 风险3: 测试环境配置复杂
|
|
**缓解措施**:
|
|
- 使用Docker容器化
|
|
- 提供详细的配置文档
|
|
- 实现环境自动化部署
|
|
|
|
---
|
|
|
|
## 时间估算
|
|
|
|
| 阶段 | 任务数 | 预计时间 | 优先级 |
|
|
|------|--------|----------|--------|
|
|
| P0 | 4 | 2-3小时 | 立即 |
|
|
| P1 | 5 | 4-6小时 | 本周 |
|
|
| P2 | 4 | 8-12小时 | 本月 |
|
|
| **总计** | **13** | **14-21小时** | - |
|
|
|
|
---
|
|
|
|
## 参考资料
|
|
|
|
- [Playwright官方文档](https://playwright.dev)
|
|
- [Vitest官方文档](https://vitest.dev)
|
|
- [金融级测试最佳实践](https://www.owasp.org/index.php/Application_Security_Testing)
|
|
- [测试覆盖率标准](https://martinfowler.com/bliki/TestCoverage.html)
|
|
- [CI/CD质量门禁](https://docs.github.com/en/actions/guides/building-and-testing)
|
|
|
|
---
|
|
|
|
**计划完成日期**: 2026-03-07
|
|
**计划版本**: 1.0
|
|
**负责人**: 测试团队
|
|
**审核人**: 技术负责人
|