# 测试覆盖率提升实施计划
> **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
| username |
```
**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 {
await this.waitForTableLoad();
return await this.userTable.locator('tbody tr').count();
}
}
```
**Step 3: 为元素添加data-testid属性**
修改 `everything-is-suitable-admin/src/views/UserManagement.vue`:
```vue
```
**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
登录
```
**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
```
**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 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 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 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 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 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 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
BASE_URL
http://localhost:8080
100
10
true
60
${BASE_URL}
/sys/auth/login
POST
true
{"username":"admin","password":"admin123"}
$.token
true
true
```
**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测试体系 | 无 | 建立 | ✅ |
| 性能测试体系 | 无 | 建立 | ✅ |