添加用户管理视图、API和状态管理文件
35 KiB
测试覆盖率提升实施计划
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天):
- 修复Admin端E2E测试URL配置错误
- 修复Admin端E2E测试元素选择器
- 修复前端单元测试Mock配置
- 实现日期工具缺失函数
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:
// 当前可能有问题的代码
export class DashboardPage extends BasePage {
constructor(page: Page) {
super(page);
this.url = '/dashboard'; // 检查这里是否正确
}
}
Step 2: 修复URL配置
// 修复后的代码
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: 验证修复
运行测试:
cd everything-is-suitable-admin
npx playwright test e2e/dashboard.spec.ts -v
预期: Dashboard页面加载测试通过
Step 4: 提交修复
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结构
在浏览器中打开用户管理页面,使用开发者工具检查元素:
<!-- 实际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:
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:
<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: 运行测试验证
cd everything-is-suitable-admin
npx playwright test e2e/user.spec.ts -v
预期: 用户管理相关测试通过
Step 5: 提交修复
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结构
<!-- 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:
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:
<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: 运行测试验证
cd everything-is-suitable-admin
npx playwright test e2e/auth.spec.ts -v
预期: 认证相关测试通过
Step 5: 提交修复
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:
// 当前可能的问题
import { vi } from 'vitest';
// Mock可能不完整
Step 2: 创建完整的Mock配置
修改 everything-is-suitable-admin/src/test/setup.ts:
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:
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:
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: 运行测试验证
cd everything-is-suitable-admin
npm run test src/test/auth.service.test.ts
预期: auth.service测试全部通过
Step 6: 提交修复
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:
// 缺失的函数测试
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:
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: 运行测试验证
cd everything-is-suitable-admin
npm run test src/test/date.test.ts
预期: 日期工具测试全部通过
Step 4: 提交实现
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:
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: 运行测试验证
cd everything-is-suitable-api/everything-is-suitable-biz
mvn test -Dtest=AlmanacServiceImplTest
预期: 测试通过
Step 3: 提交测试
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: 创建测试类
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: 运行测试验证
cd everything-is-suitable-api/everything-is-suitable-biz
mvn test -Dtest=FortuneAnalysisServiceImplTest
预期: 测试通过
Step 3: 提交测试
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: 创建测试类
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: 运行测试验证
cd everything-is-suitable-api/everything-is-suitable-biz
mvn test -Dtest=ZiweiChartServiceImplTest
预期: 测试通过
Step 3: 提交测试
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:
# 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:
# UAT测试检查清单
## 测试环境
- [ ] 测试环境已部署
- [ ] 数据库已初始化
- [ ] 测试账号已创建
- [ ] 测试数据已准备
## 功能测试
### 认证功能
- [ ] 用户注册
- [ ] 用户登录
- [ ] 用户登出
- [ ] Token刷新
- [ ] 权限验证
### 用户管理
- [ ] 用户列表查询
- [ ] 用户创建
- [ ] 用户编辑
- [ ] 用户删除
- [ ] 用户状态切换
### 角色管理
- [ ] 角色列表查询
- [ ] 角色创建
- [ ] 角色编辑
- [ ] 角色删除
- [ ] 角色权限分配
### 菜单管理
- [ ] 菜单树查询
- [ ] 菜单创建
- [ ] 菜单编辑
- [ ] 菜单删除
- [ ] 菜单排序
### 黄历服务
- [ ] 黄历查询
- [ ] 黄历搜索
- [ ] 节气信息
### 运势服务
- [ ] 日运势查询
- [ ] 月运势查询
- [ ] 年运势查询
## 性能测试
- [ ] 页面加载时间 < 2秒
- [ ] API响应时间 < 500ms
- [ ] 并发用户数 >= 100
## 安全测试
- [ ] SQL注入测试
- [ ] XSS攻击测试
- [ ] CSRF攻击测试
- [ ] 权限绕过测试
## 兼容性测试
- [ ] Chrome浏览器
- [ ] Firefox浏览器
- [ ] Safari浏览器
- [ ] Edge浏览器
## 测试结果
- 通过: ___
- 失败: ___
- 阻塞: ___
- 通过率: ___%
Step 3: 提交UAT测试框架
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 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:
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:
# 性能测试
## 测试工具
- **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
执行命令:
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测试
cd everything-is-suitable-admin
npx playwright test
预期通过率: 85%+
验证2: 运行所有前端单元测试
cd everything-is-suitable-admin
npm run test
预期通过率: 90%+
验证3: 运行所有后端单元测试
cd everything-is-suitable-api
mvn test
预期覆盖率: 75%+
验证4: 生成覆盖率报告
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测试体系 | 无 | 建立 | ✅ |
| 性能测试体系 | 无 | 建立 | ✅ |