# 学校管理模块 - 测试方案

---

| 文档信息 | 内容 |
|---------|------|
| **文档版本** | V1.0 |
| **模块名称** | 学校管理模块 |
| **编写团队** | pangu |
| **创建日期** | 2026-01-31 |

---

## 1. 测试概述

### 1.1 测试目标

- 验证学校管理模块所有功能按需求规格正确实现
- 验证业务规则正确执行
- 验证接口响应符合设计规范
- 验证数据权限控制有效
- 验证前端交互体验符合原型设计

### 1.2 测试范围

| 功能点 | 测试类型 | 优先级 |
|-------|---------|:------:|
| 学校树查询 | 接口测试、UI测试 | P0 |
| 学校列表查询 | 接口测试、UI测试 | P0 |
| 新增学校 | 接口测试、UI测试 | P0 |
| 编辑学校 | 接口测试、UI测试 | P0 |
| 删除学校 | 接口测试、UI测试 | P0 |
| 挂载年级 | 接口测试、UI测试 | P0 |
| 挂载班级 | 接口测试、UI测试 | P0 |
| 删除年级 | 接口测试、UI测试 | P1 |
| 删除班级 | 接口测试、UI测试 | P1 |
| 数据权限 | 接口测试 | P0 |
| 表单验证 | UI测试 | P0 |

### 1.3 测试环境

| 环境 | 说明 |
|-----|------|
| 前端 | Chrome 120+, Edge 120+ |
| 后端 | Java 17, Spring Boot 3.3.x |
| 数据库 | MySQL 8.0 |
| 测试工具 | Postman, JUnit 5 |

---

## 2. 接口测试用例

### 2.1 获取学校树

**接口地址**: `GET /api/school/tree`

| 用例编号 | 用例名称 | 请求参数 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-001 | 查询全部学校树 | 无 | 返回所有学校树形结构 |
| API-SCH-002 | 按区域查询学校树 | regionId=111 | 返回武昌区下学校树 |
| API-SCH-003 | 查询无学校的区域 | regionId=999 | 返回空数组 |

**API-SCH-001 详细用例**

```
请求：GET /api/school/tree
Header: Authorization: Bearer {token}

预期响应：
{
    "code": 200,
    "msg": "查询成功",
    "data": [
        {
            "id": 1,
            "type": "school",
            "name": "武汉市第一中学",
            "code": "SCH20260001",
            "status": "0",
            "regionPath": "湖北省-武汉市-武昌区",
            "children": [
                {
                    "id": 1,
                    "type": "grade",
                    "name": "七年级",
                    "schoolGradeId": 1,
                    "gradeId": 7,
                    "children": [
                        {
                            "id": 1,
                            "type": "class",
                            "name": "1班",
                            "schoolClassId": 1,
                            "classId": 1
                        }
                    ]
                }
            ]
        }
    ]
}

验证点：
1. 返回code为200
2. data为数组格式
3. 学校节点包含type="school"
4. 年级节点包含type="grade"
5. 班级节点包含type="class"
6. 层级关系正确
```

---

### 2.2 新增学校

**接口地址**: `POST /api/school`

| 用例编号 | 用例名称 | 请求参数 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-010 | 正常新增学校 | 必填项完整 | 成功，学校编码自动生成 |
| API-SCH-011 | 缺少学校名称 | schoolName为空 | 失败，返回验证错误 |
| API-SCH-012 | 缺少所属区域 | regionId为空 | 失败，返回验证错误 |
| API-SCH-013 | 缺少学校类型 | schoolType为空 | 失败，返回验证错误 |
| API-SCH-014 | 学校名称超长 | 101个字符 | 失败，返回验证错误 |

**API-SCH-010 详细用例**

```
请求：POST /api/school
Header: 
    Authorization: Bearer {token}
    Content-Type: application/json
Body:
{
    "schoolName": "武汉市测试中学",
    "schoolType": "02",
    "regionId": 111,
    "address": "武昌区测试路1号",
    "contactPerson": "张老师",
    "contactPhone": "13812345678",
    "status": "0"
}

预期响应：
{
    "code": 200,
    "msg": "操作成功"
}

验证点：
1. 返回code为200
2. 数据库中新增一条学校记录
3. school_code格式为SCH2026xxxx
4. region_path自动填充为"湖北省-武汉市-武昌区"
5. del_flag为'0'
6. create_time和create_by已填充
```

**API-SCH-011 详细用例**

```
请求：POST /api/school
Body:
{
    "schoolName": "",
    "schoolType": "02",
    "regionId": 111
}

预期响应：
{
    "code": 400,
    "msg": "学校名称不能为空"
}

验证点：
1. 返回code为400
2. msg包含错误提示
3. 数据库未新增记录
```

---

### 2.3 修改学校

**接口地址**: `PUT /api/school`

| 用例编号 | 用例名称 | 请求参数 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-020 | 正常修改学校 | 合法参数 | 成功 |
| API-SCH-021 | 修改不存在的学校 | schoolId=999 | 失败 |
| API-SCH-022 | 修改区域后更新路径 | regionId变更 | 成功，region_path更新 |

**API-SCH-020 详细用例**

```
请求：PUT /api/school
Body:
{
    "schoolId": 1,
    "schoolName": "武汉市第一中学（新）",
    "schoolType": "02",
    "regionId": 111,
    "status": "0"
}

预期响应：
{
    "code": 200,
    "msg": "操作成功"
}

验证点：
1. 返回code为200
2. 数据库school_name已更新
3. update_time和update_by已更新
```

---

### 2.4 删除学校

**接口地址**: `DELETE /api/school/{schoolId}`

| 用例编号 | 用例名称 | 前置条件 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-030 | 删除无子级的学校 | 学校无年级/班级 | 成功 |
| API-SCH-031 | 删除有年级的学校 | 学校下有年级 | 失败，提示有年级 |
| API-SCH-032 | 删除有学生的学校 | 学校被学生引用 | 失败，提示被引用 |
| API-SCH-033 | 删除不存在的学校 | schoolId=999 | 失败 |

**API-SCH-031 详细用例**

```
前置条件：
- 学校ID=1下有年级数据

请求：DELETE /api/school/1

预期响应：
{
    "code": 500,
    "msg": "该学校下存在年级数据，请先删除年级"
}

验证点：
1. 返回code非200
2. msg包含错误提示
3. 数据库记录未删除
```

**API-SCH-032 详细用例**

```
前置条件：
- 学校ID=2无年级数据
- 学校ID=2被学生信息引用

请求：DELETE /api/school/2

预期响应：
{
    "code": 500,
    "msg": "该学校已被学生信息引用，无法删除"
}

验证点：
1. 返回code非200
2. 数据库记录未删除
```

---

### 2.5 挂载年级

**接口地址**: `POST /api/school/bindGrades`

| 用例编号 | 用例名称 | 请求参数 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-040 | 正常挂载年级 | schoolId + gradeIds | 成功 |
| API-SCH-041 | 重复挂载年级 | 已存在的gradeId | 成功，忽略重复 |
| API-SCH-042 | 空年级列表 | gradeIds为空数组 | 失败，验证错误 |
| API-SCH-043 | 挂载到不存在的学校 | schoolId=999 | 失败 |

**API-SCH-040 详细用例**

```
请求：POST /api/school/bindGrades
Body:
{
    "schoolId": 1,
    "gradeIds": [1, 2, 3]
}

预期响应：
{
    "code": 200,
    "msg": "操作成功"
}

验证点：
1. 返回code为200
2. pg_school_grade表新增3条记录
3. school_id和grade_id关联正确
```

**API-SCH-041 详细用例**

```
前置条件：
- 学校ID=1已挂载年级ID=1

请求：POST /api/school/bindGrades
Body:
{
    "schoolId": 1,
    "gradeIds": [1, 4, 5]
}

预期响应：
{
    "code": 200,
    "msg": "操作成功"
}

验证点：
1. 返回成功
2. 新增2条记录（年级4、5）
3. 年级1不重复插入
```

---

### 2.6 挂载班级

**接口地址**: `POST /api/school/bindClasses`

| 用例编号 | 用例名称 | 请求参数 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-050 | 正常挂载班级 | schoolGradeId + classIds | 成功 |
| API-SCH-051 | 重复挂载班级 | 已存在的classId | 成功，忽略重复 |
| API-SCH-052 | 挂载到不存在的年级 | schoolGradeId=999 | 失败 |

---

### 2.7 删除学校年级

**接口地址**: `DELETE /api/school/grade/{schoolGradeId}`

| 用例编号 | 用例名称 | 前置条件 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-060 | 删除无班级的年级 | 年级下无班级 | 成功 |
| API-SCH-061 | 删除有班级的年级 | 年级下有班级 | 失败，提示有班级 |
| API-SCH-062 | 删除被学生引用的年级 | 年级被学生引用 | 失败，提示被引用 |

---

### 2.8 删除学校班级

**接口地址**: `DELETE /api/school/class/{schoolClassId}`

| 用例编号 | 用例名称 | 前置条件 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-070 | 删除未被引用的班级 | 班级未被学生引用 | 成功 |
| API-SCH-071 | 删除被学生引用的班级 | 班级被学生引用 | 失败，提示被引用 |

---

### 2.9 数据权限测试

| 用例编号 | 用例名称 | 测试账号 | 预期结果 |
|:------:|---------|---------|---------|
| API-SCH-080 | 超级管理员查看全部 | admin | 返回所有学校 |
| API-SCH-081 | 分公司用户查看所属区域 | 武汉分公司用户 | 只返回武汉区域学校 |
| API-SCH-082 | 分公司用户新增学校 | 武汉分公司用户 | 只能选择所属区域 |
| API-SCH-083 | 学校用户无权限 | 学校用户 | 菜单不显示或403 |

---

## 3. 功能测试用例

### 3.1 页面加载

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-001 | 页面正常加载 | 访问学校管理页面 | 页面正常显示 |
| UI-SCH-002 | 区域树加载 | 进入页面 | 左侧显示区域树 |
| UI-SCH-003 | 学校树加载 | 进入页面 | 右侧显示学校树 |
| UI-SCH-004 | 加载状态显示 | 刷新页面 | 显示加载中状态 |

---

### 3.2 区域树操作

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-010 | 区域树展开收起 | 点击父节点图标 | 子节点展开/收起 |
| UI-SCH-011 | 区域节点选中 | 点击区域节点 | 节点高亮，学校列表刷新 |
| UI-SCH-012 | 多次点击相同节点 | 重复点击同一节点 | 不重复请求 |

---

### 3.3 学校树操作

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-020 | 学校树展开 | 点击学校行展开图标 | 显示年级和班级 |
| UI-SCH-021 | 学校树收起 | 点击已展开学校的图标 | 年级班级收起 |
| UI-SCH-022 | 显示学校编码 | 查看学校行 | 学校行显示编码 |
| UI-SCH-023 | 年级班级无编码 | 查看年级/班级行 | 编码列显示"-" |

---

### 3.4 新增学校

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-030 | 打开新增弹窗 | 点击"新增学校"按钮 | 弹窗显示 |
| UI-SCH-031 | 区域默认值 | 先选择区域树节点，再点击新增 | 所属区域默认带入 |
| UI-SCH-032 | 表单验证-必填 | 留空必填项，点击确定 | 显示验证提示 |
| UI-SCH-033 | 表单验证-手机号 | 输入错误格式手机号 | 显示格式错误提示 |
| UI-SCH-034 | 正常新增 | 填写完整信息，点击确定 | 成功提示，列表刷新 |
| UI-SCH-035 | 取消新增 | 点击取消按钮 | 弹窗关闭，无数据变化 |

---

### 3.5 编辑学校

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-040 | 打开编辑弹窗 | 点击学校行"编辑"按钮 | 弹窗显示，数据回填 |
| UI-SCH-041 | 学校编码只读 | 查看编辑弹窗 | 学校编码不可修改 |
| UI-SCH-042 | 修改学校名称 | 修改名称，点击确定 | 成功，列表数据更新 |
| UI-SCH-043 | 修改所属区域 | 修改区域，点击确定 | 成功，区域路径更新 |

---

### 3.6 删除学校

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-050 | 删除确认弹窗 | 点击"删除"按钮 | 显示确认对话框 |
| UI-SCH-051 | 取消删除 | 确认框点击取消 | 对话框关闭，数据不变 |
| UI-SCH-052 | 确认删除成功 | 确认删除无关联学校 | 成功提示，列表刷新 |
| UI-SCH-053 | 删除失败提示 | 确认删除有关联学校 | 显示错误提示 |

---

### 3.7 挂载年级

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-060 | 打开年级选择弹窗 | 点击学校行"新增年级" | 弹窗显示年级列表 |
| UI-SCH-061 | 已挂载年级禁用 | 查看弹窗 | 已挂载的年级显示禁用 |
| UI-SCH-062 | 多选年级 | 勾选多个年级 | 支持多选 |
| UI-SCH-063 | 确认挂载 | 选择年级，点击确定 | 成功，学校树刷新 |
| UI-SCH-064 | 未选择年级确认 | 不选择年级，点击确定 | 提示请选择年级 |

---

### 3.8 挂载班级

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-070 | 打开班级选择弹窗 | 点击年级行"新增班级" | 弹窗显示班级列表 |
| UI-SCH-071 | 已挂载班级禁用 | 查看弹窗 | 已挂载的班级显示禁用 |
| UI-SCH-072 | 确认挂载班级 | 选择班级，点击确定 | 成功，学校树刷新 |

---

### 3.9 删除年级/班级

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-080 | 删除年级确认 | 点击年级行"删除" | 显示确认对话框 |
| UI-SCH-081 | 删除年级失败 | 确认删除有班级的年级 | 显示错误提示 |
| UI-SCH-082 | 删除年级成功 | 确认删除无班级的年级 | 成功，学校树刷新 |
| UI-SCH-083 | 删除班级 | 点击班级行"删除"并确认 | 成功，学校树刷新 |

---

### 3.10 搜索功能

| 用例编号 | 用例名称 | 测试步骤 | 预期结果 |
|:------:|---------|---------|---------|
| UI-SCH-090 | 按名称搜索 | 输入学校名称，点击搜索 | 列表过滤 |
| UI-SCH-091 | 按状态搜索 | 选择状态，点击搜索 | 列表过滤 |
| UI-SCH-092 | 组合搜索 | 输入名称+选择状态 | 列表过滤 |
| UI-SCH-093 | 重置搜索 | 点击重置按钮 | 清空条件，显示全部 |
| UI-SCH-094 | 无结果提示 | 搜索不存在的学校 | 显示"暂无数据" |

---

## 4. 边界测试

### 4.1 数据边界

| 用例编号 | 用例名称 | 测试数据 | 预期结果 |
|:------:|---------|---------|---------|
| BD-SCH-001 | 学校名称最大长度 | 100个中文字符 | 成功 |
| BD-SCH-002 | 学校名称超长 | 101个中文字符 | 验证失败 |
| BD-SCH-003 | 空学校列表 | 新区域无学校 | 显示空状态 |
| BD-SCH-004 | 大量学校 | 100所学校 | 正常显示，无卡顿 |
| BD-SCH-005 | 深层级结构 | 学校-12年级-10班级 | 正常展开显示 |

### 4.2 并发测试

| 用例编号 | 用例名称 | 测试场景 | 预期结果 |
|:------:|---------|---------|---------|
| CC-SCH-001 | 同时新增学校 | 2个用户同时新增 | 编码不冲突 |
| CC-SCH-002 | 同时挂载年级 | 2个用户同时挂载相同年级 | 不重复插入 |

---

## 5. 测试数据准备

### 5.1 测试账号

| 角色 | 账号 | 密码 | 权限 |
|-----|------|------|------|
| 超级管理员 | admin | admin123 | 全部权限 |
| 武汉分公司用户 | wuhan_user | 123456 | 武汉区域数据 |
| 学校用户 | school_user | 123456 | 本校数据 |

### 5.2 测试数据

```sql
-- 测试区域
INSERT INTO pg_region VALUES (1, 0, '湖北', 1, '0', 1, '0');
INSERT INTO pg_region VALUES (11, 1, '武汉', 2, '0,1', 1, '0');
INSERT INTO pg_region VALUES (111, 11, '武昌区', 3, '0,1,11', 1, '0');

-- 测试学校
INSERT INTO pg_school VALUES (1, 'SCH20260001', '测试学校A', '02', 111, '湖北省-武汉市-武昌区', '0', '0');
INSERT INTO pg_school VALUES (2, 'SCH20260002', '测试学校B', '02', 111, '湖北省-武汉市-武昌区', '0', '0');

-- 测试年级
INSERT INTO pg_grade VALUES (1, 'GRD001', '一年级', 1, '0', '0');
INSERT INTO pg_grade VALUES (7, 'GRD007', '七年级', 7, '0', '0');

-- 测试班级
INSERT INTO pg_class VALUES (1, 'CLS001', '1班', 1, '0', '0');
INSERT INTO pg_class VALUES (2, 'CLS002', '2班', 2, '0', '0');

-- 测试关联
INSERT INTO pg_school_grade VALUES (1, 1, 7, 1, '0', '0');
INSERT INTO pg_school_class VALUES (1, 1, 1, 1, '0', '0');
```

---

## 6. 缺陷管理

### 6.1 缺陷等级定义

| 等级 | 说明 | 处理时限 |
|:----:|------|---------|
| P0 | 阻塞性缺陷，功能完全不可用 | 立即修复 |
| P1 | 严重缺陷，核心功能受影响 | 1个工作日 |
| P2 | 一般缺陷，功能可用但有问题 | 3个工作日 |
| P3 | 轻微缺陷，体验问题 | 下个版本 |

### 6.2 缺陷跟踪

| 缺陷编号 | 标题 | 等级 | 发现人 | 发现日期 | 状态 | 修复人 | 验证人 |
|:------:|------|:----:|------|---------|:----:|------|------|
| | | | | | | | |

---

## 7. 测试报告模板

### 7.1 测试概要

| 项目 | 内容 |
|-----|------|
| 测试模块 | 学校管理 |
| 测试版本 | |
| 测试周期 | |
| 测试人员 | |

### 7.2 测试结果

| 测试类型 | 用例数 | 通过 | 失败 | 阻塞 | 通过率 |
|---------|:------:|:----:|:----:|:----:|:------:|
| 接口测试 | | | | | |
| 功能测试 | | | | | |
| 边界测试 | | | | | |
| **合计** | | | | | |

### 7.3 缺陷统计

| 等级 | 发现数 | 已修复 | 待修复 | 验证通过 |
|:----:|:------:|:------:|:------:|:--------:|
| P0 | | | | |
| P1 | | | | |
| P2 | | | | |
| P3 | | | | |

### 7.4 测试结论

- [ ] 通过测试，可以发布
- [ ] 有待修复问题，暂不发布
- [ ] 建议：

---

*文档结束*
