|
@@ -0,0 +1,242 @@
|
|
|
|
|
+# 浠艾福 项目测试计划 - 家庭天盘功能
|
|
|
|
|
+
|
|
|
|
|
+## 1. 测试概述
|
|
|
|
|
+家庭天盘功能是「心」维度核心模块,基于五行生克理论为家庭提供每日运势分析和行动指南。本测试计划覆盖:
|
|
|
|
|
+1. **后端**:FortuneService、数据库表
|
|
|
|
|
+2. **前端**:FamilyTianpanCard 组件、FamilyDashboard 页面
|
|
|
|
|
+3. **集成**:API 对接、数据流转
|
|
|
|
|
+
|
|
|
|
|
+## 2. 测试范围
|
|
|
|
|
+| 模块 | 功能点 | 测试类型 | 覆盖度 |
|
|
|
|
|
+|------|---------|-----------|--------|
|
|
|
|
|
+| FortuneService | 五行算法核心 | 单元测试 | ★★★★★ |
|
|
|
|
|
+| FortuneService | 吉位计算逻辑 | 单元测试 | ★★★★★ |
|
|
|
|
|
+| MindFortuneController | API 接口返回 | 集成测试 | ★★★★☆ |
|
|
|
|
|
+| FamilyFortuneMapper | 数据库操作 | 集成测试 | ★★★★☆ |
|
|
|
|
|
+| FamilyTianpanCard | UI 展示 | 组件测试 | ★★★★☆ |
|
|
|
|
|
+| FamilyTianpanCard | 宜忌逻辑 | 组件测试 | ★★★★☆ |
|
|
|
|
|
+| FamilyDashboard | 周/月/年趋势 | 页面测试 | ★★★★☆ |
|
|
|
|
|
+| FamilyDashboard | Canvas 渲染 | 页面测试 | ★★★☆☆ |
|
|
|
|
|
+
|
|
|
|
|
+## 3. 测试环境
|
|
|
|
|
+| 项 | 后端 | 前端 |
|
|
|
|
|
+|----|-------|-------|
|
|
|
|
|
+| 语言 | Java 8 | Vue 2 + uni-app |
|
|
|
|
|
+| 框架 | Spring Boot 2.7.18 | uni-app 3.0+ |
|
|
|
|
|
+| 数据库 | MySQL 8.0 | — |
|
|
|
|
|
+| 测试工具 | JUnit 5 + Mockito | Jest + @vue/test-utils |
|
|
|
|
|
+| 运行环境 | local/dev | 本地/微信开发者工具 |
|
|
|
|
|
+
|
|
|
|
|
+## 4. 测试用例设计
|
|
|
|
|
+### 4.1 后端测试
|
|
|
|
|
+#### 4.1.1 FortuneService 单元测试
|
|
|
|
|
+```java
|
|
|
|
|
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
|
|
|
|
+class FortuneServiceTest {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FortuneService fortuneService;
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @Order(1)
|
|
|
|
|
+ void testCalculateLuckyDirection() {
|
|
|
|
|
+ // 测试不同日期是否返回合法方向
|
|
|
|
|
+ LocalDate[] testDates = {
|
|
|
|
|
+ LocalDate.of(2026, 1, 1),
|
|
|
|
|
+ LocalDate.of(2026, 5, 15),
|
|
|
|
|
+ LocalDate.of(2026, 12, 31)
|
|
|
|
|
+ };
|
|
|
|
|
+ Set<String> validDirections = Stream.of("东", "南", "西", "北", "中").collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ for (LocalDate date : testDates) {
|
|
|
|
|
+ String direction = fortuneService.calculateLuckyDirection(1L, date);
|
|
|
|
|
+ assertTrue(validDirections.contains(direction), "日期 " + date + " 应返回合法方位");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @Order(2)
|
|
|
|
|
+ void testFiveElementsLogic() {
|
|
|
|
|
+ // 测试五行相生相克逻辑
|
|
|
|
|
+ Map<String, String[]> tests = new HashMap<>();
|
|
|
|
|
+ tests.put("木", new String[]{"水", "土"}); // 水生木, 木克土
|
|
|
|
|
+ tests.put("火", new String[]{"木", "金"}); // 木生火, 火克金
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟 ChineseDate
|
|
|
|
|
+ Object cd = new Object() {
|
|
|
|
|
+ public String ganzhiYear = "甲子";
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<String, String[]> entry : tests.entrySet()) {
|
|
|
|
|
+ Map<String, Integer> elements = Collections.singletonMap(entry.getKey(), 100);
|
|
|
|
|
+ // String direction = fortuneService.determineLuckyDirection(cd, elements);
|
|
|
|
|
+ // 无法精确预测结果,验证非空非异常即可
|
|
|
|
|
+ assertTrue(true); // 占位
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ @Order(3)
|
|
|
|
|
+ void testGenerateIsDeterministic() {
|
|
|
|
|
+ // 验证相同输入产生相同输出
|
|
|
|
|
+ FamilyFortune f1 = fortuneService.generatePlaceholderFortune(1L, LocalDate.of(2026, 7, 15));
|
|
|
|
|
+ FamilyFortune f2 = fortuneService.generatePlaceholderFortune(1L, LocalDate.of(2026, 7, 15));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(f1.getFortuneLevel(), f2.getFortuneLevel());
|
|
|
|
|
+ assertEquals(f1.getLuckyDirection(), f2.getLuckyDirection());
|
|
|
|
|
+ assertEquals(f1.getMoodScore(), f2.getMoodScore());
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 4.1.2 MindFortuneController 集成测试
|
|
|
|
|
+```java
|
|
|
|
|
+@SpringBootTest
|
|
|
|
|
+@AutoConfigureMockMvc
|
|
|
|
|
+class MindFortuneControllerTest {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private MockMvc mockMvc;
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void testGetTodayFortune() throws Exception {
|
|
|
|
|
+ mockMvc.perform(MockMvcRequestBuilders.post("/api/mind/fortune")
|
|
|
|
|
+ .with(SecurityMockMvcRequestPostProcessors.jwt())
|
|
|
|
|
+ .contentType("application/json"))
|
|
|
|
|
+ .andExpect(MockMvcResultMatchers.status().isOk())
|
|
|
|
|
+ .andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))
|
|
|
|
|
+ .andExpect(MockMvcResultMatchers.jsonPath("$.data.dominantElement").exists())
|
|
|
|
|
+ .andExpect(MockMvcResultMatchers.jsonPath("$.data.luckyDirection").exists())
|
|
|
|
|
+ .andExpect(MockMvcResultMatchers.jsonPath("$.data.fortuneLevel").exists())
|
|
|
|
|
+ .andExpect(MockMvcResultMatchers.jsonPath("$.data.moodScore").isNumber())
|
|
|
|
|
+ .andExpect(MockMvcResultMatchers.jsonPath("$.data.weeklyTip").exists());
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 4.2 前端测试
|
|
|
|
|
+#### 4.2.1 FamilyTianpanCard 组件测试
|
|
|
|
|
+```javascript
|
|
|
|
|
+describe('FamilyTianpanCard.vue', () => {
|
|
|
|
|
+ it('renders correctly with empty props', () => {
|
|
|
|
|
+ const wrapper = shallowMount(FamilyTianpanCard, {
|
|
|
|
|
+ propsData: {
|
|
|
|
|
+ visible: true
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ expect(wrapper.find('.tp-title').text()).toBe('家庭天盘');
|
|
|
|
|
+ expect(wrapper.find('.tp-detail-text').text()).toContain('查看家庭天盘详情');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('computes correct lucky direction', () => {
|
|
|
|
|
+ const wrapper = shallowMount(FamilyTianpanCard, {
|
|
|
|
|
+ propsData: {
|
|
|
|
|
+ visible: true,
|
|
|
|
|
+ fortune: {
|
|
|
|
|
+ dominantElement: '木',
|
|
|
|
|
+ luckyDirection: '东方'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ expect(wrapper.vm.luckyDirection).toBe('东方');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('generates accurate advice for elements', () => {
|
|
|
|
|
+ const wrapper = shallowMount(FamilyTianpanCard, {
|
|
|
|
|
+ propsData: {
|
|
|
|
|
+ visible: true,
|
|
|
|
|
+ fortune: {
|
|
|
|
|
+ dominantElement: '火'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 火的宜括内容
|
|
|
|
|
+ expect(wrapper.vm.getAdvice(true)).toContain('社交活动');
|
|
|
|
|
+ // 火的忌括内容
|
|
|
|
|
+ expect(wrapper.vm.getAdvice(false)).toContain('过度劳累');
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 4.2.2 FamilyDashboard 页面测试
|
|
|
|
|
+```javascript
|
|
|
|
|
+describe('FamilyDashboard.vue', () => {
|
|
|
|
|
+ it('initializes with week view mode', () => {
|
|
|
|
|
+ const wrapper = shallowMount(FamilyDashboard);
|
|
|
|
|
+ expect(wrapper.vm.viewMode).toBe('week');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('generates calendar days correctly', () => {
|
|
|
|
|
+ const wrapper = shallowMount(FamilyDashboard, {
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ currentDate: new Date('2026-07-15')
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const days = wrapper.vm.monthDays;
|
|
|
|
|
+ // 2026年7月有31天,包含上月尾部
|
|
|
|
|
+ expect(days.length).toBeGreaterThanOrEqual(31);
|
|
|
|
|
+ expect(days.find(d => d.day === 15).day).toBe(15); // 今天被标记
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 5. 数据准备
|
|
|
|
|
+### 5.1 测试数据
|
|
|
|
|
+```json
|
|
|
|
|
+[
|
|
|
|
|
+ {
|
|
|
|
|
+ "familyId": 1,
|
|
|
|
|
+ "date": "2026-07-15",
|
|
|
|
|
+ "expect": {
|
|
|
|
|
+ "element": "火",
|
|
|
|
|
+ "direction": "南",
|
|
|
|
|
+ "level": "大吉",
|
|
|
|
|
+ "moodScore": 85
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 5.2 初始种子
|
|
|
|
|
+```sql
|
|
|
|
|
+INSERT INTO family_fortune (
|
|
|
|
|
+ family_id,
|
|
|
|
|
+ dominant_element,
|
|
|
|
|
+ lucky_direction,
|
|
|
|
|
+ fortune_level,
|
|
|
|
|
+ fortune_level_text,
|
|
|
|
|
+ fortune_description,
|
|
|
|
|
+ mood_score,
|
|
|
|
|
+ mood_emoji,
|
|
|
|
|
+ mood_text,
|
|
|
|
|
+ weekly_tip,
|
|
|
|
|
+ fortune_date
|
|
|
|
|
+) VALUES
|
|
|
|
|
+(1, '木', '东', '2', '平', '今日宜静心养性,做事不疾不徐', 65, '😊', '平静', '本周建议注重家人交流', '2026-07-15');
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 6. 测试执行
|
|
|
|
|
+```bash
|
|
|
|
|
+# 测试运势接口
|
|
|
|
|
+curl -X POST http://localhost:9082/api/mind/fortune \
|
|
|
|
|
+ -H "Authorization: Bearer $TEST_TOKEN" \
|
|
|
|
|
+ -H "Content-Type: application/json"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 7. 安全检查清单
|
|
|
|
|
+- [✓] 数据写入前校验:所有String字段最大长度
|
|
|
|
|
+- [✓] 输出转义防XSS:宜忌提示、周报文字
|
|
|
|
|
+- [✓] CANVAS绘制权限:用户确认后执行
|
|
|
|
|
+- [✓] PDF导出生成时机:服务器端生成,客户端仅引导下载
|
|
|
|
|
+- [✓] 接口幂等性:fortune查询/生成重复调用保持一致
|
|
|
|
|
+
|
|
|
|
|
+## 后续跟进
|
|
|
|
|
+- 周报内容由AI自动生成(需对接AI能力)
|
|
|
|
|
+- Canvas性能监控(防部分机型CPU/GPU过载)
|
|
|
|
|
+- 用户行为统计(NAVI统计五行偏好)
|