# 测试覆盖率提升实施计划 > **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测试体系 | 无 | 建立 | ✅ | | 性能测试体系 | 无 | 建立 | ✅ |