08ea5fbe98
添加用户管理视图、API和状态管理文件
1469 lines
35 KiB
Markdown
1469 lines
35 KiB
Markdown
# 测试覆盖率提升实施计划
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** 修复现有测试失败并提升测试覆盖率至生产级别标准
|
|
|
|
**Architecture:** 采用渐进式修复策略,优先修复高优先级问题,逐步提升测试覆盖率。使用TDD方法论,确保每个修复都有测试验证。
|
|
|
|
**Tech Stack:**
|
|
- 后端: Spring Boot 3.4.1 + JUnit 5 + Mockito + JaCoCo
|
|
- 前端: Vue 3 + Vitest + Playwright
|
|
- 测试工具: pytest (API测试)
|
|
|
|
---
|
|
|
|
## 执行策略
|
|
|
|
### 优先级排序
|
|
|
|
**P0 - 立即执行** (1-3天):
|
|
1. 修复Admin端E2E测试URL配置错误
|
|
2. 修复Admin端E2E测试元素选择器
|
|
3. 修复前端单元测试Mock配置
|
|
4. 实现日期工具缺失函数
|
|
|
|
**P1 - 短期执行** (3-7天):
|
|
5. 补充后端Service层单元测试
|
|
6. 修复Python E2E测试框架
|
|
|
|
**P2 - 中期执行** (1-2周):
|
|
7. 建立UAT测试体系
|
|
8. 建立性能测试体系
|
|
|
|
---
|
|
|
|
## 阶段一:修复Admin端E2E测试 (P0)
|
|
|
|
### Task 1: 修复Dashboard URL配置错误
|
|
|
|
**问题**: 导航到无效URL `http://localhost:5174undefined`
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/e2e/pages/dashboard-page.ts`
|
|
- Test: `everything-is-suitable-admin/e2e/dashboard.spec.ts`
|
|
|
|
**Step 1: 检查Dashboard页面URL配置**
|
|
|
|
检查文件 `everything-is-suitable-admin/e2e/pages/dashboard-page.ts`:
|
|
|
|
```typescript
|
|
// 当前可能有问题的代码
|
|
export class DashboardPage extends BasePage {
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.url = '/dashboard'; // 检查这里是否正确
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 修复URL配置**
|
|
|
|
```typescript
|
|
// 修复后的代码
|
|
export class DashboardPage extends BasePage {
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.url = '/dashboard'; // 确保不以 / 开头时添加 baseURL
|
|
}
|
|
|
|
async navigate() {
|
|
await this.page.goto(this.url); // 使用相对路径
|
|
await this.waitForLoad();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 验证修复**
|
|
|
|
运行测试:
|
|
```bash
|
|
cd everything-is-suitable-admin
|
|
npx playwright test e2e/dashboard.spec.ts -v
|
|
```
|
|
|
|
预期: Dashboard页面加载测试通过
|
|
|
|
**Step 4: 提交修复**
|
|
|
|
```bash
|
|
git add e2e/pages/dashboard-page.ts
|
|
git commit -m "fix(e2e): 修复Dashboard页面URL配置错误"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: 修复用户管理页面元素选择器
|
|
|
|
**问题**: 用户列表表格元素选择器问题
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/e2e/pages/user-management-page.ts`
|
|
- Test: `everything-is-suitable-admin/e2e/user.spec.ts`
|
|
|
|
**Step 1: 检查用户管理页面实际DOM结构**
|
|
|
|
在浏览器中打开用户管理页面,使用开发者工具检查元素:
|
|
|
|
```html
|
|
<!-- 实际DOM结构示例 -->
|
|
<a-table
|
|
class="ant-table"
|
|
data-testid="user-table"
|
|
>
|
|
<tbody class="ant-table-tbody">
|
|
<tr class="ant-table-row">
|
|
<td>username</td>
|
|
</tr>
|
|
</tbody>
|
|
</a-table>
|
|
```
|
|
|
|
**Step 2: 更新元素选择器**
|
|
|
|
修改 `everything-is-suitable-admin/e2e/pages/user-management-page.ts`:
|
|
|
|
```typescript
|
|
export class UserManagementPage extends BasePage {
|
|
// 使用更稳定的选择器
|
|
private userTable = this.page.locator('[data-testid="user-table"]');
|
|
private createUserButton = this.page.locator('button:has-text("创建用户")');
|
|
private searchInput = this.page.locator('input[placeholder*="搜索"]');
|
|
|
|
async waitForTableLoad() {
|
|
await this.userTable.waitFor({ state: 'visible' });
|
|
await this.page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async getUserCount(): Promise<number> {
|
|
await this.waitForTableLoad();
|
|
return await this.userTable.locator('tbody tr').count();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 为元素添加data-testid属性**
|
|
|
|
修改 `everything-is-suitable-admin/src/views/UserManagement.vue`:
|
|
|
|
```vue
|
|
<template>
|
|
<div class="user-management">
|
|
<a-table
|
|
:data-source="users"
|
|
data-testid="user-table"
|
|
:columns="columns"
|
|
>
|
|
<!-- 表格内容 -->
|
|
</a-table>
|
|
|
|
<a-button
|
|
type="primary"
|
|
data-testid="create-user-button"
|
|
@click="showCreateModal"
|
|
>
|
|
创建用户
|
|
</a-button>
|
|
|
|
<a-input
|
|
v-model:value="searchText"
|
|
data-testid="search-input"
|
|
placeholder="搜索用户名或邮箱"
|
|
/>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
**Step 4: 运行测试验证**
|
|
|
|
```bash
|
|
cd everything-is-suitable-admin
|
|
npx playwright test e2e/user.spec.ts -v
|
|
```
|
|
|
|
预期: 用户管理相关测试通过
|
|
|
|
**Step 5: 提交修复**
|
|
|
|
```bash
|
|
git add e2e/pages/user-management-page.ts src/views/UserManagement.vue
|
|
git commit -m "fix(e2e): 修复用户管理页面元素选择器并添加data-testid"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: 修复认证模块元素选择器
|
|
|
|
**问题**: 登录/登出相关元素选择器错误
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/e2e/pages/login-page.ts`
|
|
- Test: `everything-is-suitable-admin/e2e/auth.spec.ts`
|
|
|
|
**Step 1: 检查登录页面DOM结构**
|
|
|
|
```html
|
|
<!-- Login.vue 实际结构 -->
|
|
<a-form class="login-form">
|
|
<a-form-item>
|
|
<a-input v-model:value="form.username" data-testid="username-input" />
|
|
</a-form-item>
|
|
<a-form-item>
|
|
<a-input-password v-model:value="form.password" data-testid="password-input" />
|
|
</a-form-item>
|
|
<a-button type="primary" html-type="submit" data-testid="login-button">
|
|
登录
|
|
</a-button>
|
|
</a-form>
|
|
```
|
|
|
|
**Step 2: 更新登录页面选择器**
|
|
|
|
修改 `everything-is-suitable-admin/e2e/pages/login-page.ts`:
|
|
|
|
```typescript
|
|
export class LoginPage extends BasePage {
|
|
private usernameInput = this.page.locator('[data-testid="username-input"]');
|
|
private passwordInput = this.page.locator('[data-testid="password-input"]');
|
|
private loginButton = this.page.locator('[data-testid="login-button"]');
|
|
private errorMessage = this.page.locator('.ant-message-error');
|
|
|
|
async login(username: string, password: string) {
|
|
await this.usernameInput.fill(username);
|
|
await this.passwordInput.fill(password);
|
|
await this.loginButton.click();
|
|
}
|
|
|
|
async expectLoginSuccess() {
|
|
await this.page.waitForURL('**/dashboard');
|
|
}
|
|
|
|
async expectLoginError(message: string) {
|
|
await this.errorMessage.waitFor({ state: 'visible' });
|
|
await expect(this.errorMessage).toContainText(message);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: 为登录页面添加data-testid**
|
|
|
|
修改 `everything-is-suitable-admin/src/views/Login.vue`:
|
|
|
|
```vue
|
|
<template>
|
|
<div class="login-container">
|
|
<a-form class="login-form" @finish="handleLogin">
|
|
<a-form-item name="username">
|
|
<a-input
|
|
v-model:value="form.username"
|
|
data-testid="username-input"
|
|
placeholder="用户名"
|
|
/>
|
|
</a-form-item>
|
|
|
|
<a-form-item name="password">
|
|
<a-input-password
|
|
v-model:value="form.password"
|
|
data-testid="password-input"
|
|
placeholder="密码"
|
|
/>
|
|
</a-form-item>
|
|
|
|
<a-button
|
|
type="primary"
|
|
html-type="submit"
|
|
data-testid="login-button"
|
|
:loading="loading"
|
|
>
|
|
登录
|
|
</a-button>
|
|
</a-form>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
**Step 4: 运行测试验证**
|
|
|
|
```bash
|
|
cd everything-is-suitable-admin
|
|
npx playwright test e2e/auth.spec.ts -v
|
|
```
|
|
|
|
预期: 认证相关测试通过
|
|
|
|
**Step 5: 提交修复**
|
|
|
|
```bash
|
|
git add e2e/pages/login-page.ts src/views/Login.vue
|
|
git commit -m "fix(e2e): 修复认证模块元素选择器并添加data-testid"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: 修复前端单元测试Mock配置
|
|
|
|
**问题**: API和Service测试Mock配置不正确
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/src/test/setup.ts`
|
|
- Modify: `everything-is-suitable-admin/vitest.config.ts`
|
|
- Test: `everything-is-suitable-admin/src/test/auth.service.test.ts`
|
|
|
|
**Step 1: 检查当前Mock配置**
|
|
|
|
查看 `everything-is-suitable-admin/src/test/setup.ts`:
|
|
|
|
```typescript
|
|
// 当前可能的问题
|
|
import { vi } from 'vitest';
|
|
|
|
// Mock可能不完整
|
|
```
|
|
|
|
**Step 2: 创建完整的Mock配置**
|
|
|
|
修改 `everything-is-suitable-admin/src/test/setup.ts`:
|
|
|
|
```typescript
|
|
import { vi } from 'vitest';
|
|
import { config } from '@vue/test-utils';
|
|
|
|
// Mock localStorage
|
|
const localStorageMock = {
|
|
getItem: vi.fn(),
|
|
setItem: vi.fn(),
|
|
removeItem: vi.fn(),
|
|
clear: vi.fn(),
|
|
};
|
|
global.localStorage = localStorageMock as any;
|
|
|
|
// Mock sessionStorage
|
|
global.sessionStorage = localStorageMock as any;
|
|
|
|
// Mock axios
|
|
vi.mock('axios', () => ({
|
|
default: {
|
|
create: vi.fn(() => ({
|
|
get: vi.fn(),
|
|
post: vi.fn(),
|
|
put: vi.fn(),
|
|
delete: vi.fn(),
|
|
interceptors: {
|
|
request: { use: vi.fn() },
|
|
response: { use: vi.fn() },
|
|
},
|
|
})),
|
|
},
|
|
}));
|
|
|
|
// Mock router
|
|
vi.mock('vue-router', () => ({
|
|
createRouter: vi.fn(),
|
|
createWebHistory: vi.fn(),
|
|
useRoute: vi.fn(() => ({
|
|
params: {},
|
|
query: {},
|
|
path: '/',
|
|
})),
|
|
useRouter: vi.fn(() => ({
|
|
push: vi.fn(),
|
|
replace: vi.fn(),
|
|
go: vi.fn(),
|
|
})),
|
|
}));
|
|
|
|
// 全局测试配置
|
|
config.global.stubs = {
|
|
RouterLink: true,
|
|
RouterView: true,
|
|
};
|
|
```
|
|
|
|
**Step 3: 更新vitest配置**
|
|
|
|
修改 `everything-is-suitable-admin/vitest.config.ts`:
|
|
|
|
```typescript
|
|
import { defineConfig } from 'vitest/config';
|
|
import vue from '@vitejs/plugin-vue';
|
|
import { resolve } from 'path';
|
|
|
|
export default defineConfig({
|
|
plugins: [vue()],
|
|
resolve: {
|
|
alias: {
|
|
'@': resolve(__dirname, 'src')
|
|
}
|
|
},
|
|
test: {
|
|
globals: true,
|
|
environment: 'jsdom',
|
|
setupFiles: ['./src/test/setup.ts'],
|
|
exclude: [
|
|
'node_modules/',
|
|
'dist/',
|
|
'e2e/',
|
|
'**/*.config.ts',
|
|
'**/*.config.js'
|
|
],
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'json', 'html'],
|
|
exclude: [
|
|
'node_modules/',
|
|
'src/test/',
|
|
'**/*.d.ts',
|
|
'**/*.config.*',
|
|
'**/mockData',
|
|
'e2e/'
|
|
]
|
|
},
|
|
// 添加Mock相关配置
|
|
deps: {
|
|
inline: ['ant-design-vue'],
|
|
},
|
|
}
|
|
});
|
|
```
|
|
|
|
**Step 4: 修复auth.service.test.ts**
|
|
|
|
修改 `everything-is-suitable-admin/src/test/auth.service.test.ts`:
|
|
|
|
```typescript
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { authService } from '@/services/auth.service';
|
|
import request from '@/utils/request';
|
|
|
|
// Mock request
|
|
vi.mock('@/utils/request', () => ({
|
|
default: {
|
|
post: vi.fn(),
|
|
get: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('AuthService', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should login successfully', async () => {
|
|
const mockResponse = {
|
|
token: 'test-token',
|
|
user: {
|
|
id: 1,
|
|
username: 'admin',
|
|
email: 'admin@example.com',
|
|
},
|
|
};
|
|
|
|
vi.mocked(request.post).mockResolvedValue(mockResponse);
|
|
|
|
const result = await authService.login({
|
|
username: 'admin',
|
|
password: 'admin123',
|
|
});
|
|
|
|
expect(request.post).toHaveBeenCalledWith('/sys/auth/login', {
|
|
username: 'admin',
|
|
password: 'admin123',
|
|
});
|
|
expect(result).toEqual(mockResponse);
|
|
});
|
|
|
|
it('should logout successfully', async () => {
|
|
vi.mocked(request.post).mockResolvedValue({});
|
|
|
|
await authService.logout();
|
|
|
|
expect(request.post).toHaveBeenCalledWith('/sys/auth/logout');
|
|
});
|
|
|
|
it('should refresh token successfully', async () => {
|
|
const mockResponse = {
|
|
token: 'new-token',
|
|
user: {
|
|
id: 1,
|
|
username: 'admin',
|
|
},
|
|
};
|
|
|
|
vi.mocked(request.post).mockResolvedValue(mockResponse);
|
|
|
|
const result = await authService.refreshToken('old-token');
|
|
|
|
expect(request.post).toHaveBeenCalledWith('/sys/auth/refresh/old-token');
|
|
expect(result).toEqual(mockResponse);
|
|
});
|
|
});
|
|
```
|
|
|
|
**Step 5: 运行测试验证**
|
|
|
|
```bash
|
|
cd everything-is-suitable-admin
|
|
npm run test src/test/auth.service.test.ts
|
|
```
|
|
|
|
预期: auth.service测试全部通过
|
|
|
|
**Step 6: 提交修复**
|
|
|
|
```bash
|
|
git add src/test/setup.ts vitest.config.ts src/test/auth.service.test.ts
|
|
git commit -m "fix(test): 修复前端单元测试Mock配置"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: 实现日期工具缺失函数
|
|
|
|
**问题**: 日期工具测试失败,缺少函数实现
|
|
|
|
**Files:**
|
|
- Modify: `everything-is-suitable-admin/src/utils/date.ts`
|
|
- Test: `everything-is-suitable-admin/src/test/date.test.ts`
|
|
|
|
**Step 1: 检查测试用例**
|
|
|
|
查看 `everything-is-suitable-admin/src/test/date.test.ts`:
|
|
|
|
```typescript
|
|
// 缺失的函数测试
|
|
it('should check if year is leap year', () => {
|
|
expect(isLeapYear(2024)).toBe(true);
|
|
expect(isLeapYear(2023)).toBe(false);
|
|
});
|
|
|
|
it('should get days in month', () => {
|
|
expect(getDaysInMonth(2024, 2)).toBe(29);
|
|
expect(getDaysInMonth(2023, 2)).toBe(28);
|
|
});
|
|
|
|
it('should get week number', () => {
|
|
expect(getWeekNumber(new Date('2024-01-01'))).toBe(1);
|
|
});
|
|
|
|
it('should calculate age', () => {
|
|
expect(getAge(new Date('1990-01-01'))).toBe(34);
|
|
});
|
|
|
|
it('should format duration', () => {
|
|
expect(formatDuration(3661)).toBe('1小时1分钟1秒');
|
|
});
|
|
|
|
it('should parse duration', () => {
|
|
expect(parseDuration('1小时30分钟')).toBe(5400);
|
|
});
|
|
```
|
|
|
|
**Step 2: 实现缺失的函数**
|
|
|
|
修改 `everything-is-suitable-admin/src/utils/date.ts`:
|
|
|
|
```typescript
|
|
import dayjs from 'dayjs';
|
|
|
|
/**
|
|
* 检查是否为闰年
|
|
*/
|
|
export function isLeapYear(year: number): boolean {
|
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
}
|
|
|
|
/**
|
|
* 获取指定月份的天数
|
|
*/
|
|
export function getDaysInMonth(year: number, month: number): number {
|
|
return new Date(year, month, 0).getDate();
|
|
}
|
|
|
|
/**
|
|
* 获取日期所在的周数
|
|
*/
|
|
export function getWeekNumber(date: Date): number {
|
|
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
const dayNum = d.getUTCDay() || 7;
|
|
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
|
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
|
}
|
|
|
|
/**
|
|
* 计算年龄
|
|
*/
|
|
export function getAge(birthDate: Date): number {
|
|
const today = new Date();
|
|
let age = today.getFullYear() - birthDate.getFullYear();
|
|
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
|
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
|
age--;
|
|
}
|
|
|
|
return age;
|
|
}
|
|
|
|
/**
|
|
* 格式化时长(秒转换为可读格式)
|
|
*/
|
|
export function formatDuration(seconds: number): string {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
const secs = seconds % 60;
|
|
|
|
const parts: string[] = [];
|
|
if (hours > 0) parts.push(`${hours}小时`);
|
|
if (minutes > 0) parts.push(`${minutes}分钟`);
|
|
if (secs > 0 || parts.length === 0) parts.push(`${secs}秒`);
|
|
|
|
return parts.join('');
|
|
}
|
|
|
|
/**
|
|
* 解析时长字符串为秒数
|
|
*/
|
|
export function parseDuration(durationStr: string): number {
|
|
let totalSeconds = 0;
|
|
|
|
const hourMatch = durationStr.match(/(\d+)\s*小时/);
|
|
const minuteMatch = durationStr.match(/(\d+)\s*分钟/);
|
|
const secondMatch = durationStr.match(/(\d+)\s*秒/);
|
|
|
|
if (hourMatch) totalSeconds += parseInt(hourMatch[1]) * 3600;
|
|
if (minuteMatch) totalSeconds += parseInt(minuteMatch[1]) * 60;
|
|
if (secondMatch) totalSeconds += parseInt(secondMatch[1]);
|
|
|
|
return totalSeconds;
|
|
}
|
|
```
|
|
|
|
**Step 3: 运行测试验证**
|
|
|
|
```bash
|
|
cd everything-is-suitable-admin
|
|
npm run test src/test/date.test.ts
|
|
```
|
|
|
|
预期: 日期工具测试全部通过
|
|
|
|
**Step 4: 提交实现**
|
|
|
|
```bash
|
|
git add src/utils/date.ts
|
|
git commit -m "feat(utils): 实现日期工具缺失函数"
|
|
```
|
|
|
|
---
|
|
|
|
## 阶段二:补充后端Service层单元测试 (P1)
|
|
|
|
### Task 6: 创建AlmanacService单元测试
|
|
|
|
**问题**: Service层测试覆盖率0%
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-api/everything-is-suitable-biz/src/test/java/io/destiny/biz/service/impl/AlmanacServiceImplTest.java`
|
|
- Test: 运行JUnit测试
|
|
|
|
**Step 1: 创建测试类**
|
|
|
|
创建文件 `everything-is-suitable-api/everything-is-suitable-biz/src/test/java/io/destiny/biz/service/impl/AlmanacServiceImplTest.java`:
|
|
|
|
```java
|
|
package io.destiny.biz.service.impl;
|
|
|
|
import io.destiny.biz.dto.AlmanacDTO;
|
|
import io.destiny.biz.exception.AlmanacException;
|
|
import io.destiny.biz.repository.AlmanacRepository;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
import org.mockito.InjectMocks;
|
|
import org.mockito.Mock;
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
import reactor.core.publisher.Mono;
|
|
import reactor.test.StepVerifier;
|
|
|
|
import java.time.LocalDate;
|
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
import static org.mockito.Mockito.*;
|
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
class AlmanacServiceImplTest {
|
|
|
|
@Mock
|
|
private AlmanacRepository almanacRepository;
|
|
|
|
@InjectMocks
|
|
private AlmanacServiceImpl almanacService;
|
|
|
|
private AlmanacDTO testAlmanac;
|
|
|
|
@BeforeEach
|
|
void setUp() {
|
|
testAlmanac = AlmanacDTO.builder()
|
|
.date(LocalDate.now())
|
|
.lunarDate("正月初一")
|
|
.suit("祭祀 祈福")
|
|
.avoid("开仓 动土")
|
|
.build();
|
|
}
|
|
|
|
@Test
|
|
void getAlmanacByDate_shouldReturnAlmanac() {
|
|
// Given
|
|
LocalDate date = LocalDate.now();
|
|
when(almanacRepository.findByDate(date))
|
|
.thenReturn(Mono.just(testAlmanac));
|
|
|
|
// When
|
|
Mono<AlmanacDTO> result = almanacService.getAlmanacByDate(date);
|
|
|
|
// Then
|
|
StepVerifier.create(result)
|
|
.expectNext(testAlmanac)
|
|
.verifyComplete();
|
|
|
|
verify(almanacRepository).findByDate(date);
|
|
}
|
|
|
|
@Test
|
|
void getAlmanacByDate_shouldThrowExceptionWhenNotFound() {
|
|
// Given
|
|
LocalDate date = LocalDate.now();
|
|
when(almanacRepository.findByDate(date))
|
|
.thenReturn(Mono.empty());
|
|
|
|
// When & Then
|
|
StepVerifier.create(almanacService.getAlmanacByDate(date))
|
|
.expectError(AlmanacException.class)
|
|
.verify();
|
|
|
|
verify(almanacRepository).findByDate(date);
|
|
}
|
|
|
|
@Test
|
|
void getAlmanacByDateRange_shouldReturnAlmanacList() {
|
|
// Given
|
|
LocalDate startDate = LocalDate.now();
|
|
LocalDate endDate = startDate.plusDays(7);
|
|
|
|
when(almanacRepository.findByDateBetween(startDate, endDate))
|
|
.thenReturn(Mono.just(testAlmanac));
|
|
|
|
// When
|
|
Mono<AlmanacDTO> result = almanacService.getAlmanacByDateRange(startDate, endDate);
|
|
|
|
// Then
|
|
StepVerifier.create(result)
|
|
.expectNext(testAlmanac)
|
|
.verifyComplete();
|
|
|
|
verify(almanacRepository).findByDateBetween(startDate, endDate);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 运行测试验证**
|
|
|
|
```bash
|
|
cd everything-is-suitable-api/everything-is-suitable-biz
|
|
mvn test -Dtest=AlmanacServiceImplTest
|
|
```
|
|
|
|
预期: 测试通过
|
|
|
|
**Step 3: 提交测试**
|
|
|
|
```bash
|
|
git add src/test/java/io/destiny/biz/service/impl/AlmanacServiceImplTest.java
|
|
git commit -m "test(service): 添加AlmanacService单元测试"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 7: 创建FortuneAnalysisService单元测试
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-api/everything-is-suitable-biz/src/test/java/io/destiny/biz/service/impl/FortuneAnalysisServiceImplTest.java`
|
|
|
|
**Step 1: 创建测试类**
|
|
|
|
```java
|
|
package io.destiny.biz.service.impl;
|
|
|
|
import io.destiny.biz.dto.FortuneDTO;
|
|
import io.destiny.biz.enums.FortuneType;
|
|
import io.destiny.biz.repository.FortuneRepository;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
import org.mockito.InjectMocks;
|
|
import org.mockito.Mock;
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
import reactor.core.publisher.Mono;
|
|
import reactor.test.StepVerifier;
|
|
|
|
import java.time.LocalDate;
|
|
|
|
import static org.mockito.Mockito.*;
|
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
class FortuneAnalysisServiceImplTest {
|
|
|
|
@Mock
|
|
private FortuneRepository fortuneRepository;
|
|
|
|
@InjectMocks
|
|
private FortuneAnalysisServiceImpl fortuneService;
|
|
|
|
private FortuneDTO testFortune;
|
|
|
|
@BeforeEach
|
|
void setUp() {
|
|
testFortune = FortuneDTO.builder()
|
|
.date(LocalDate.now())
|
|
.fortuneType(FortuneType.DAILY)
|
|
.overallScore(85)
|
|
.careerScore(90)
|
|
.wealthScore(80)
|
|
.loveScore(85)
|
|
.healthScore(75)
|
|
.build();
|
|
}
|
|
|
|
@Test
|
|
void getDailyFortune_shouldReturnFortune() {
|
|
// Given
|
|
LocalDate date = LocalDate.now();
|
|
when(fortuneRepository.findByDateAndType(date, FortuneType.DAILY))
|
|
.thenReturn(Mono.just(testFortune));
|
|
|
|
// When
|
|
Mono<FortuneDTO> result = fortuneService.getDailyFortune(date);
|
|
|
|
// Then
|
|
StepVerifier.create(result)
|
|
.expectNext(testFortune)
|
|
.verifyComplete();
|
|
|
|
verify(fortuneRepository).findByDateAndType(date, FortuneType.DAILY);
|
|
}
|
|
|
|
@Test
|
|
void getMonthlyFortune_shouldReturnFortune() {
|
|
// Given
|
|
int year = 2024;
|
|
int month = 3;
|
|
when(fortuneRepository.findByYearAndMonthAndType(year, month, FortuneType.MONTHLY))
|
|
.thenReturn(Mono.just(testFortune));
|
|
|
|
// When
|
|
Mono<FortuneDTO> result = fortuneService.getMonthlyFortune(year, month);
|
|
|
|
// Then
|
|
StepVerifier.create(result)
|
|
.expectNext(testFortune)
|
|
.verifyComplete();
|
|
|
|
verify(fortuneRepository).findByYearAndMonthAndType(year, month, FortuneType.MONTHLY);
|
|
}
|
|
|
|
@Test
|
|
void getYearlyFortune_shouldReturnFortune() {
|
|
// Given
|
|
int year = 2024;
|
|
when(fortuneRepository.findByYearAndType(year, FortuneType.YEARLY))
|
|
.thenReturn(Mono.just(testFortune));
|
|
|
|
// When
|
|
Mono<FortuneDTO> result = fortuneService.getYearlyFortune(year);
|
|
|
|
// Then
|
|
StepVerifier.create(result)
|
|
.expectNext(testFortune)
|
|
.verifyComplete();
|
|
|
|
verify(fortuneRepository).findByYearAndType(year, FortuneType.YEARLY);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 运行测试验证**
|
|
|
|
```bash
|
|
cd everything-is-suitable-api/everything-is-suitable-biz
|
|
mvn test -Dtest=FortuneAnalysisServiceImplTest
|
|
```
|
|
|
|
预期: 测试通过
|
|
|
|
**Step 3: 提交测试**
|
|
|
|
```bash
|
|
git add src/test/java/io/destiny/biz/service/impl/FortuneAnalysisServiceImplTest.java
|
|
git commit -m "test(service): 添加FortuneAnalysisService单元测试"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 8: 创建ZiweiChartService单元测试
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-api/everything-is-suitable-biz/src/test/java/io/destiny/biz/service/impl/ZiweiChartServiceImplTest.java`
|
|
|
|
**Step 1: 创建测试类**
|
|
|
|
```java
|
|
package io.destiny.biz.service.impl;
|
|
|
|
import io.destiny.biz.dto.ZiweiChartDTO;
|
|
import io.destiny.biz.util.ZiweiAlgorithmUtil;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
import org.mockito.InjectMocks;
|
|
import org.mockito.Mock;
|
|
import org.mockito.MockedStatic;
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
import static org.mockito.Mockito.*;
|
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
class ZiweiChartServiceImplTest {
|
|
|
|
@InjectMocks
|
|
private ZiweiChartServiceImpl ziweiService;
|
|
|
|
private ZiweiChartDTO testChart;
|
|
|
|
@BeforeEach
|
|
void setUp() {
|
|
testChart = ZiweiChartDTO.builder()
|
|
.birthTime(LocalDateTime.of(1990, 1, 1, 12, 0))
|
|
.mingGong("命宫")
|
|
.ziweiStar("紫微星")
|
|
.build();
|
|
}
|
|
|
|
@Test
|
|
void calculateChart_shouldReturnCorrectChart() {
|
|
// Given
|
|
LocalDateTime birthTime = LocalDateTime.of(1990, 1, 1, 12, 0);
|
|
String gender = "男";
|
|
|
|
try (MockedStatic<ZiweiAlgorithmUtil> mockedUtil = mockStatic(ZiweiAlgorithmUtil.class)) {
|
|
mockedUtil.when(() -> ZiweiAlgorithmUtil.calculateMingGong(any()))
|
|
.thenReturn("命宫");
|
|
mockedUtil.when(() -> ZiweiAlgorithmUtil.calculateZiweiStar(any()))
|
|
.thenReturn("紫微星");
|
|
|
|
// When
|
|
ZiweiChartDTO result = ziweiService.calculateChart(birthTime, gender);
|
|
|
|
// Then
|
|
assertNotNull(result);
|
|
assertEquals("命宫", result.getMingGong());
|
|
assertEquals("紫微星", result.getZiweiStar());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
void calculateChart_shouldThrowExceptionForInvalidInput() {
|
|
// Given
|
|
LocalDateTime birthTime = null;
|
|
String gender = "男";
|
|
|
|
// When & Then
|
|
assertThrows(IllegalArgumentException.class, () -> {
|
|
ziweiService.calculateChart(birthTime, gender);
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: 运行测试验证**
|
|
|
|
```bash
|
|
cd everything-is-suitable-api/everything-is-suitable-biz
|
|
mvn test -Dtest=ZiweiChartServiceImplTest
|
|
```
|
|
|
|
预期: 测试通过
|
|
|
|
**Step 3: 提交测试**
|
|
|
|
```bash
|
|
git add src/test/java/io/destiny/biz/service/impl/ZiweiChartServiceImplTest.java
|
|
git commit -m "test(service): 添加ZiweiChartService单元测试"
|
|
```
|
|
|
|
---
|
|
|
|
## 阶段三:建立UAT测试体系 (P2)
|
|
|
|
### Task 9: 创建UAT测试框架
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-test/uat/README.md`
|
|
- Create: `everything-is-suitable-test/uat/test-scenarios.md`
|
|
- Create: `everything-is-suitable-test/uat/uat-checklist.md`
|
|
|
|
**Step 1: 创建UAT测试场景文档**
|
|
|
|
创建文件 `everything-is-suitable-test/uat/test-scenarios.md`:
|
|
|
|
```markdown
|
|
# UAT测试场景
|
|
|
|
## 场景1: 用户注册-登录-查看个人信息
|
|
|
|
**前置条件**:
|
|
- 系统已启动
|
|
- 数据库已初始化
|
|
|
|
**测试步骤**:
|
|
1. 打开系统登录页面
|
|
2. 点击"注册"按钮
|
|
3. 填写注册表单:
|
|
- 用户名: testuser001
|
|
- 密码: Test@123
|
|
- 邮箱: test@example.com
|
|
- 手机: 13800138000
|
|
4. 提交注册
|
|
5. 验证注册成功提示
|
|
6. 使用新账号登录
|
|
7. 进入个人信息页面
|
|
8. 验证个人信息显示正确
|
|
|
|
**预期结果**:
|
|
- ✅ 注册成功
|
|
- ✅ 登录成功
|
|
- ✅ 个人信息显示正确
|
|
|
|
---
|
|
|
|
## 场景2: 管理员创建用户-分配角色-验证权限
|
|
|
|
**前置条件**:
|
|
- 管理员账号已登录
|
|
|
|
**测试步骤**:
|
|
1. 进入用户管理页面
|
|
2. 点击"创建用户"按钮
|
|
3. 填写用户信息:
|
|
- 用户名: newuser001
|
|
- 密码: User@123
|
|
- 邮箱: newuser@example.com
|
|
4. 提交创建
|
|
5. 进入角色管理页面
|
|
6. 创建新角色"测试角色"
|
|
7. 为角色分配权限: 用户查看、角色查看
|
|
8. 将角色分配给新用户
|
|
9. 使用新用户登录
|
|
10. 验证权限:
|
|
- ✅ 可访问用户管理页面
|
|
- ✅ 可查看用户列表
|
|
- ❌ 不可创建用户
|
|
- ❌ 不可删除用户
|
|
|
|
**预期结果**:
|
|
- ✅ 用户创建成功
|
|
- ✅ 角色创建成功
|
|
- ✅ 权限分配正确
|
|
- ✅ 权限验证通过
|
|
|
|
---
|
|
|
|
## 场景3: 黄历查询-运势分析-结果展示
|
|
|
|
**前置条件**:
|
|
- 用户已登录
|
|
|
|
**测试步骤**:
|
|
1. 进入黄历查询页面
|
|
2. 选择日期: 2024年3月20日
|
|
3. 点击查询
|
|
4. 验证黄历信息显示:
|
|
- ✅ 农历日期
|
|
- ✅ 宜忌事项
|
|
- ✅ 节气信息
|
|
5. 进入运势分析页面
|
|
6. 选择日期类型: 日运势
|
|
7. 选择日期: 2024年3月20日
|
|
8. 点击查询
|
|
9. 验证运势信息显示:
|
|
- ✅ 综合运势评分
|
|
- ✅ 事业运势
|
|
- ✅ 财运
|
|
- ✅ 爱情运势
|
|
- ✅ 健康运势
|
|
|
|
**预期结果**:
|
|
- ✅ 黄历查询成功
|
|
- ✅ 运势分析成功
|
|
- ✅ 结果展示完整
|
|
```
|
|
|
|
**Step 2: 创建UAT测试检查清单**
|
|
|
|
创建文件 `everything-is-suitable-test/uat/uat-checklist.md`:
|
|
|
|
```markdown
|
|
# UAT测试检查清单
|
|
|
|
## 测试环境
|
|
|
|
- [ ] 测试环境已部署
|
|
- [ ] 数据库已初始化
|
|
- [ ] 测试账号已创建
|
|
- [ ] 测试数据已准备
|
|
|
|
## 功能测试
|
|
|
|
### 认证功能
|
|
- [ ] 用户注册
|
|
- [ ] 用户登录
|
|
- [ ] 用户登出
|
|
- [ ] Token刷新
|
|
- [ ] 权限验证
|
|
|
|
### 用户管理
|
|
- [ ] 用户列表查询
|
|
- [ ] 用户创建
|
|
- [ ] 用户编辑
|
|
- [ ] 用户删除
|
|
- [ ] 用户状态切换
|
|
|
|
### 角色管理
|
|
- [ ] 角色列表查询
|
|
- [ ] 角色创建
|
|
- [ ] 角色编辑
|
|
- [ ] 角色删除
|
|
- [ ] 角色权限分配
|
|
|
|
### 菜单管理
|
|
- [ ] 菜单树查询
|
|
- [ ] 菜单创建
|
|
- [ ] 菜单编辑
|
|
- [ ] 菜单删除
|
|
- [ ] 菜单排序
|
|
|
|
### 黄历服务
|
|
- [ ] 黄历查询
|
|
- [ ] 黄历搜索
|
|
- [ ] 节气信息
|
|
|
|
### 运势服务
|
|
- [ ] 日运势查询
|
|
- [ ] 月运势查询
|
|
- [ ] 年运势查询
|
|
|
|
## 性能测试
|
|
|
|
- [ ] 页面加载时间 < 2秒
|
|
- [ ] API响应时间 < 500ms
|
|
- [ ] 并发用户数 >= 100
|
|
|
|
## 安全测试
|
|
|
|
- [ ] SQL注入测试
|
|
- [ ] XSS攻击测试
|
|
- [ ] CSRF攻击测试
|
|
- [ ] 权限绕过测试
|
|
|
|
## 兼容性测试
|
|
|
|
- [ ] Chrome浏览器
|
|
- [ ] Firefox浏览器
|
|
- [ ] Safari浏览器
|
|
- [ ] Edge浏览器
|
|
|
|
## 测试结果
|
|
|
|
- 通过: ___
|
|
- 失败: ___
|
|
- 阻塞: ___
|
|
- 通过率: ___%
|
|
```
|
|
|
|
**Step 3: 提交UAT测试框架**
|
|
|
|
```bash
|
|
git add everything-is-suitable-test/uat/
|
|
git commit -m "docs(uat): 建立UAT测试体系"
|
|
```
|
|
|
|
---
|
|
|
|
## 阶段四:建立性能测试体系 (P2)
|
|
|
|
### Task 10: 创建性能测试脚本
|
|
|
|
**Files:**
|
|
- Create: `everything-is-suitable-test/performance/README.md`
|
|
- Create: `everything-is-suitable-test/performance/jmeter/login-test.jmx`
|
|
- Create: `everything-is-suitable-test/performance/k6/api-load-test.js`
|
|
|
|
**Step 1: 创建JMeter登录性能测试脚本**
|
|
|
|
创建文件 `everything-is-suitable-test/performance/jmeter/login-test.jmx`:
|
|
|
|
```xml
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<jmeterTestPlan version="1.2" properties="5.0">
|
|
<hashTree>
|
|
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="登录性能测试">
|
|
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
|
|
<collectionProp name="Arguments.arguments">
|
|
<elementProp name="BASE_URL" elementType="Argument">
|
|
<stringProp name="Argument.name">BASE_URL</stringProp>
|
|
<stringProp name="Argument.value">http://localhost:8080</stringProp>
|
|
</elementProp>
|
|
</collectionProp>
|
|
</elementProp>
|
|
</TestPlan>
|
|
<hashTree>
|
|
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="用户登录">
|
|
<stringProp name="ThreadGroup.num_threads">100</stringProp>
|
|
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
|
|
<boolProp name="ThreadGroup.scheduler">true</boolProp>
|
|
<stringProp name="ThreadGroup.duration">60</stringProp>
|
|
</ThreadGroup>
|
|
<hashTree>
|
|
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="登录请求">
|
|
<stringProp name="HTTPSampler.domain">${BASE_URL}</stringProp>
|
|
<stringProp name="HTTPSampler.path">/sys/auth/login</stringProp>
|
|
<stringProp name="HTTPSampler.method">POST</stringProp>
|
|
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
|
|
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
|
|
<collectionProp name="Arguments.arguments">
|
|
<elementProp name="" elementType="HTTPArgument">
|
|
<stringProp name="Argument.value">{"username":"admin","password":"admin123"}</stringProp>
|
|
</elementProp>
|
|
</collectionProp>
|
|
</elementProp>
|
|
</HTTPSamplerProxy>
|
|
<hashTree>
|
|
<JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="验证Token">
|
|
<stringProp name="JSON_PATH">$.token</stringProp>
|
|
<boolProp name="JSONVALIDATION">true</boolProp>
|
|
</JSONPathAssertion>
|
|
<hashTree/>
|
|
</hashTree>
|
|
</hashTree>
|
|
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="聚合报告">
|
|
<boolProp name="ResultCollector.error_logging">true</boolProp>
|
|
</ResultCollector>
|
|
<hashTree/>
|
|
</hashTree>
|
|
</hashTree>
|
|
</jmeterTestPlan>
|
|
```
|
|
|
|
**Step 2: 创建k6 API负载测试脚本**
|
|
|
|
创建文件 `everything-is-suitable-test/performance/k6/api-load-test.js`:
|
|
|
|
```javascript
|
|
import http from 'k6/http';
|
|
import { check, sleep } from 'k6';
|
|
import { Rate } from 'k6/metrics';
|
|
|
|
const errorRate = new Rate('errors');
|
|
|
|
export const options = {
|
|
stages: [
|
|
{ duration: '30s', target: 20 }, // 逐渐增加到20个用户
|
|
{ duration: '1m', target: 20 }, // 保持20个用户1分钟
|
|
{ duration: '30s', target: 50 }, // 增加到50个用户
|
|
{ duration: '1m', target: 50 }, // 保持50个用户1分钟
|
|
{ duration: '30s', target: 0 }, // 逐渐减少到0
|
|
],
|
|
thresholds: {
|
|
http_req_duration: ['p(99)<500'], // 99%的请求必须在500ms内完成
|
|
errors: ['rate<0.1'], // 错误率必须小于10%
|
|
},
|
|
};
|
|
|
|
const BASE_URL = 'http://localhost:8080';
|
|
|
|
export default function () {
|
|
// 登录获取Token
|
|
const loginRes = http.post(`${BASE_URL}/sys/auth/login`, JSON.stringify({
|
|
username: 'admin',
|
|
password: 'admin123',
|
|
}), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
|
|
check(loginRes, {
|
|
'登录成功': (r) => r.status === 200,
|
|
'返回Token': (r) => r.json('token') !== undefined,
|
|
});
|
|
|
|
errorRate.add(loginRes.status !== 200);
|
|
|
|
if (loginRes.status === 200) {
|
|
const token = loginRes.json('token');
|
|
|
|
// 查询用户列表
|
|
const usersRes = http.get(`${BASE_URL}/sys/user`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
check(usersRes, {
|
|
'用户列表查询成功': (r) => r.status === 200,
|
|
'返回用户数据': (r) => r.json('records') !== undefined,
|
|
});
|
|
|
|
errorRate.add(usersRes.status !== 200);
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
```
|
|
|
|
**Step 3: 创建性能测试README**
|
|
|
|
创建文件 `everything-is-suitable-test/performance/README.md`:
|
|
|
|
```markdown
|
|
# 性能测试
|
|
|
|
## 测试工具
|
|
|
|
- **JMeter**: 用于模拟大量用户并发访问
|
|
- **k6**: 用于API负载测试
|
|
|
|
## 测试场景
|
|
|
|
### 1. 登录性能测试
|
|
|
|
**目标**: 验证登录接口在高并发下的性能
|
|
|
|
**工具**: JMeter
|
|
|
|
**执行命令**:
|
|
```bash
|
|
jmeter -n -t jmeter/login-test.jmx -l results/login-test.jtl
|
|
```
|
|
|
|
**性能指标**:
|
|
- 并发用户数: 100
|
|
- 平均响应时间 < 500ms
|
|
- 错误率 < 1%
|
|
|
|
---
|
|
|
|
### 2. API负载测试
|
|
|
|
**目标**: 验证API在高负载下的性能
|
|
|
|
**工具**: k6
|
|
|
|
**执行命令**:
|
|
```bash
|
|
k6 run k6/api-load-test.js
|
|
```
|
|
|
|
**性能指标**:
|
|
- 最大并发用户数: 50
|
|
- P99响应时间 < 500ms
|
|
- 错误率 < 10%
|
|
|
|
---
|
|
|
|
## 性能基准
|
|
|
|
| 指标 | 目标值 | 说明 |
|
|
|------|--------|------|
|
|
| 页面加载时间 | < 2秒 | 首屏加载时间 |
|
|
| API响应时间 | < 500ms | P99响应时间 |
|
|
| 并发用户数 | >= 100 | 系统支持的最大并发数 |
|
|
| 吞吐量 | >= 1000 TPS | 系统每秒处理事务数 |
|
|
| 错误率 | < 1% | 请求失败率 |
|
|
|
|
---
|
|
|
|
## 测试报告
|
|
|
|
测试完成后,查看以下报告:
|
|
|
|
- JMeter: `results/login-test.jtl`
|
|
- k6: 控制台输出
|
|
```
|
|
|
|
**Step 4: 提交性能测试框架**
|
|
|
|
```bash
|
|
git add everything-is-suitable-test/performance/
|
|
git commit -m "feat(performance): 建立性能测试体系"
|
|
```
|
|
|
|
---
|
|
|
|
## 执行总结
|
|
|
|
完成所有任务后,执行以下验证:
|
|
|
|
### 验证1: 运行所有E2E测试
|
|
|
|
```bash
|
|
cd everything-is-suitable-admin
|
|
npx playwright test
|
|
```
|
|
|
|
预期通过率: 85%+
|
|
|
|
### 验证2: 运行所有前端单元测试
|
|
|
|
```bash
|
|
cd everything-is-suitable-admin
|
|
npm run test
|
|
```
|
|
|
|
预期通过率: 90%+
|
|
|
|
### 验证3: 运行所有后端单元测试
|
|
|
|
```bash
|
|
cd everything-is-suitable-api
|
|
mvn test
|
|
```
|
|
|
|
预期覆盖率: 75%+
|
|
|
|
### 验证4: 生成覆盖率报告
|
|
|
|
```bash
|
|
cd everything-is-suitable-api
|
|
mvn jacoco:report
|
|
```
|
|
|
|
查看报告: `everything-is-suitable-biz/target/site/jacoco/index.html`
|
|
|
|
---
|
|
|
|
## 预期成果
|
|
|
|
完成本计划后,预期达到以下目标:
|
|
|
|
| 指标 | 当前值 | 目标值 | 提升 |
|
|
|------|--------|--------|------|
|
|
| 后端指令覆盖率 | 58% | 75% | +17% |
|
|
| 后端Service层覆盖率 | 0% | 80% | +80% |
|
|
| 前端单元测试通过率 | 55.5% | 90% | +34.5% |
|
|
| E2E测试通过率 | 45.1% | 85% | +39.9% |
|
|
| UAT测试体系 | 无 | 建立 | ✅ |
|
|
| 性能测试体系 | 无 | 建立 | ✅ |
|