Files
railwaysystem/docs/visual/testing.html
T

636 lines
32 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>质量与验证体系 - RailwaySystem SYFJGYD学员管理系统</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%231d4ed8' d='M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2.23l2-2H14l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-3.58-4-8-4z'/></svg>">
<link rel="stylesheet" href="assets/docs.css">
<style>
/* 测试金字塔层级展示 */
.pyramid-wrap {
background: #ffffff;
border: 1px solid var(--border-light);
border-radius: var(--radius-lg);
padding: 2rem;
box-shadow: var(--shadow-card);
margin-bottom: 2.5rem;
}
.pyramid-layers {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 860px;
margin: 1.5rem auto 0;
}
.pyramid-tier {
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: 1rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.pyramid-tier:hover {
transform: translateX(4px);
box-shadow: var(--shadow-card-hover);
}
.tier-4 { background: #eff6ff; border-color: #bfdbfe; margin: 0 12%; }
.tier-3 { background: #f0fdf4; border-color: #bbf7d0; margin: 0 8%; }
.tier-2 { background: #fefce8; border-color: #fef08a; margin: 0 4%; }
.tier-1 { background: #f8fafc; border-color: #e2e8f0; margin: 0 0; }
.tier-info h3 {
font-size: 0.98rem;
font-weight: 700;
color: var(--navy-900);
display: flex;
align-items: center;
gap: 0.5rem;
}
.tier-info p {
font-size: 0.82rem;
color: var(--text-secondary);
margin-top: 0.2rem;
}
/* Scenario 卡片网格 */
.scenario-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
@media (max-width: 900px) {
.scenario-grid { grid-template-columns: 1fr; }
.tier-4, .tier-3, .tier-2, .tier-1 { margin: 0; }
}
.scenario-card {
background: #ffffff;
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: 1.5rem;
box-shadow: var(--shadow-card);
display: flex;
flex-direction: column;
}
.scenario-card:hover {
border-color: var(--border-medium);
box-shadow: var(--shadow-card-hover);
}
.scenario-point-list {
list-style: none;
margin: 0.85rem 0;
flex: 1;
}
.scenario-point-item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.35rem 0;
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.6;
}
.scenario-point-item::before {
content: '✓';
color: var(--pass-color);
font-weight: 700;
flex-shrink: 0;
}
/* 浏览器验收卡片网格 */
.browser-category-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.25rem;
}
@media (max-width: 960px) {
.browser-category-grid { grid-template-columns: 1fr; }
}
.browser-card {
background: #ffffff;
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
padding: 1.25rem;
box-shadow: var(--shadow-card);
}
/* Cleanup 口径对比 */
.cleanup-box {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.25rem;
margin-top: 1rem;
}
@media (max-width: 768px) {
.cleanup-box { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<!-- 顶部主导航 -->
<nav class="navbar">
<div class="nav-brand-wrap">
<a href="index.html" class="nav-brand">
<svg viewBox="0 0 24 24"><path d="M12 2c-4 0-8 .5-8 4v9.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h2.23l2-2H14l2 2h2v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V6c0-3.5-3.58-4-8-4zM7.5 17c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm9 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6H6V6h12v5z"/></svg>
<div>
<div class="brand-title">RailwaySystem</div>
<div class="brand-sub">SYFJGYD学员管理系统</div>
</div>
</a>
<span class="brand-pill">Final Candidate · 待正式部署</span>
</div>
<ul class="nav-menu">
<li><a href="index.html" class="nav-link">首页</a></li>
<li><a href="business-flow.html" class="nav-link">业务</a></li>
<li><a href="architecture.html" class="nav-link">系统</a></li>
<li><a href="testing.html" class="nav-link active">质量</a></li>
<li><a href="release-status.html" class="nav-link">发布</a></li>
<li><a href="modules.html" class="nav-link">14大业务模块清单</a></li>
<li><a href="data-model.html" class="nav-link">实体关系模型(ER)</a></li>
<li><a href="deployment.html" class="nav-link">部署拓扑与脚本Delta</a></li>
<li><a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/handbook/08-deployment-and-release.md" target="_blank" rel="noopener noreferrer" class="nav-handbook-link">&gt; 查阅部署发布手册</a></li>
<li><a href="http://192.168.139.218:9980/sict/railwaysystem/-/blob/feat/cicd-pipeline/docs/README.md" target="_blank" rel="noopener noreferrer" class="nav-back-link">&larr; 文档中心</a></li>
</ul>
</nav>
<!-- 页面主体容器 -->
<main class="page-container">
<header class="page-hero">
<div class="hero-pretitle">Quality Assurance & Verification</div>
<h1 class="hero-title-main">质量指标与全层级验证矩阵</h1>
<p class="hero-lead-text">
严格基于 <code>scripts/qa/core-business-scenario-runner.py</code>、<code>docs/_meta/core-business-test-matrix.md</code> 及真实浏览器端到端验收报告生成。杜绝任何未经代码或测试证实的扩写。
</p>
</header>
<!-- 1. 顶部真实指标 Dashboard (7个精准真实KPI) -->
<div class="kpi-dashboard">
<div class="kpi-card kpi-pass">
<div class="kpi-value">331 / 331</div>
<div class="kpi-label">后端单元与集成测试</div>
<div class="kpi-sub">2026-09-17一期综合验收基线:331项后端测试通过</div>
</div>
<div class="kpi-card kpi-pass">
<div class="kpi-value">6 / 6</div>
<div class="kpi-label">核心业务场景套件</div>
<div class="kpi-sub">SC-01 ~ SC-06 全部通过</div>
</div>
<div class="kpi-card kpi-info">
<div class="kpi-value">110</div>
<div class="kpi-label">真实 HTTP API 探测</div>
<div class="kpi-sub">自动化请求全量记录</div>
</div>
<div class="kpi-card kpi-pass">
<div class="kpi-value">0</div>
<div class="kpi-label">服务端 500 异常</div>
<div class="kpi-sub">HTTP 500 Count = 0</div>
</div>
<div class="kpi-card kpi-pass">
<div class="kpi-value">11 / 11</div>
<div class="kpi-label">Kingbase 持久化断言</div>
<div class="kpi-sub">实体与外键完整性无孤儿</div>
</div>
<div class="kpi-card kpi-pass">
<div class="kpi-value">PASS</div>
<div class="kpi-label">浏览器真实 UI 验收</div>
<div class="kpi-sub">Final Browser Acceptance</div>
</div>
<div class="kpi-card kpi-pass">
<div class="kpi-value">0</div>
<div class="kpi-label">控制台阻断错误</div>
<div class="kpi-sub">Console Error = 0</div>
</div>
</div>
<!-- 2. 测试金字塔 (Testing Pyramid) -->
<section class="content-section">
<div class="section-header">
<div>
<h2 class="section-title">分层质量防御金字塔</h2>
<p class="section-subtitle">从离线单元逻辑、跨模块端到端集成、物理数据库落库校验到浏览器界面交互的四级防护体系。</p>
</div>
</div>
<div class="pyramid-wrap">
<div class="pyramid-layers">
<!-- 顶层: 浏览器验收 -->
<div class="pyramid-tier tier-4">
<div class="tier-info">
<h3><span>第 4 层:真实浏览器端到端验收 (Final Browser Acceptance)</span> <span class="badge badge-pass">PASS</span></h3>
<p>真实浏览器端到端交互:登录、表单建档、计划排课、冲突 Warning 弹窗确认、报到确认及 Web 班牌。</p>
</div>
<span class="card-code">Console Error: 0</span>
</div>
<!-- 第 3 层: 数据库持久化只读断言 -->
<div class="pyramid-tier tier-3">
<div class="tier-info">
<h3><span>第 3 层:Kingbase 数据库落库只读断言 (DB Assertions)</span> <span class="badge badge-pass">PASS</span></h3>
<p>11 项物理 SQL 只读断言:外键无孤儿记录、场地 XOR 互斥、逻辑删除标志、结训完成时间及退训防破坏保护。</p>
</div>
<span class="card-code">11 / 11 PASS</span>
</div>
<!-- 第 2 层: 场景集成测试套件 -->
<div class="pyramid-tier tier-2">
<div class="tier-info">
<h3><span>第 2 层:核心业务场景集成测试 (SC-01 ~ SC-06)</span> <span class="badge badge-pass">PASS</span></h3>
<p>Python 自动化套件覆盖正向全链路、报名拦截、排课预检 Warning/Block、班牌降级、结训事务及报到考勤闭环。</p>
</div>
<span class="card-code">6 / 6 PASS · 110 Requests</span>
</div>
<!-- 底层: 单元与契约测试 -->
<div class="pyramid-tier tier-1">
<div class="tier-info">
<h3><span>第 1 层:后端单元与集成测试 (Backend Unit / Integration)</span> <span class="badge badge-pass">PASS</span></h3>
<p>Maven Surefire 全量测试集,涵盖状态机流转、排课冲突算法、脱敏工具类与权限注解断言。</p>
</div>
<span class="card-code">331 PASS (2026-09-17验收基线)</span>
</div>
</div>
</div>
</section>
<!-- 3. SC-01 ~ SC-06 场景测试网格 (精简密度:3个关键事实 + 折叠详情) -->
<section class="content-section">
<div class="section-header">
<div>
<h2 class="section-title">核心业务场景集成测试 (SCENARIO-01 ~ SCENARIO-06)</h2>
<p class="section-subtitle">严格对齐 <code>scripts/qa/core-business-scenario-runner.py</code> 真实代码逻辑,默认呈现关键结论,点击可查阅详细步骤。</p>
</div>
</div>
<div class="scenario-grid">
<!-- SC-01 -->
<div class="scenario-card">
<div class="card-top">
<span class="badge badge-pass">SCENARIO-01 · PASS</span>
<span class="card-code">Happy Path</span>
</div>
<h3 class="card-title">完整正向业务闭环全链</h3>
<ul class="scenario-point-list">
<li class="scenario-point-item"><strong>基础资源就绪</strong>:成功创建 Teacher、Course、Classroom 及 TrainingSite。</li>
<li class="scenario-point-item"><strong>开班排课全链</strong>:拟定计划、绑定课程师资、学员入班待报到、课表聚合正常。</li>
<li class="scenario-point-item"><strong>自动结训完成</strong>:计划流转至已结束,活动学员自动结训完成并写入 complete_time。</li>
</ul>
<details class="rule-collapse" style="margin-top: 0.6rem;">
<summary style="font-size: 0.78rem; color: var(--rail-blue); cursor: pointer; font-weight: 600;">展开 15 步详细交互验证</summary>
<div class="rule-content" style="padding-top: 0.4rem; font-size: 0.78rem; color: var(--text-secondary); line-height: 1.6;">
<div>· 开班计划采用 32 位 UUID 主键,初始状态为 0:草稿。</div>
<div>· Timetable 课表接口准确聚合课程名称、授课教师及场地名。</div>
<div>· 调用 <code>POST /train/plans/{id}/complete</code> 强事务批量流转状态。</div>
</div>
</details>
<div class="card-footer-action">
<span class="card-code">覆盖 15 步交互验证</span>
<span class="badge badge-pass">验证通过</span>
</div>
</div>
<!-- SC-02 -->
<div class="scenario-card">
<div class="card-top">
<span class="badge badge-pass">SCENARIO-02 · PASS</span>
<span class="card-code">Enrollment Rules</span>
</div>
<h3 class="card-title">学员报名异常规则与状态区分</h3>
<ul class="scenario-point-list">
<li class="scenario-point-item"><strong>重复报名拦截</strong>:同一计划重复报名触发唯一索引,准确返回 <code>40030</code>。</li>
<li class="scenario-point-item"><strong>容量超限拦截</strong>:入班人数超过计划 <code>recruit_num</code> 上限时拦截返回 <code>40031</code>。</li>
<li class="scenario-point-item"><strong>状态严格区分</strong>:学员主档状态 (正常/停用) 与参训状态机独立解耦流转。</li>
</ul>
<details class="rule-collapse" style="margin-top: 0.6rem;">
<summary style="font-size: 0.78rem; color: var(--rail-blue); cursor: pointer; font-weight: 600;">展开异常校验与拦截细节</summary>
<div class="rule-content" style="padding-top: 0.4rem; font-size: 0.78rem; color: var(--text-secondary); line-height: 1.6;">
<div>· 不存在的非法学员严格拒绝入班。</div>
<div>· 停用学员 (status=0) 严禁报名入班。</div>
<div>· 参训状态机严格维护 0待报到/1已报到/2请假/3退训/4已完成。</div>
</div>
</details>
<div class="card-footer-action">
<span class="card-code">业务错误码精准契约</span>
<span class="badge badge-pass">验证通过</span>
</div>
</div>
<!-- SC-03 -->
<div class="scenario-card">
<div class="card-top">
<span class="badge badge-pass">SCENARIO-03 · PASS</span>
<span class="card-code">Scheduling Engine</span>
</div>
<h3 class="card-title">排课预检 Warning 与 Hard Block 硬阻断</h3>
<ul class="scenario-point-list">
<li class="scenario-point-item"><strong>四维冲突预检</strong>:教师冲突硬阻断;教室/场地/班级重叠返回 Warning 提示。</li>
<li class="scenario-point-item"><strong>非阻断合规</strong>:Warning 级别由教务员确认保存,严格禁止 force 旁路参数。</li>
<li class="scenario-point-item"><strong>互斥与锁定</strong>:场地 XOR 互斥强校验;已结训计划强阻断变更 (<code>40012</code>)。</li>
</ul>
<details class="rule-collapse" style="margin-top: 0.6rem;">
<summary style="font-size: 0.78rem; color: var(--rail-blue); cursor: pointer; font-weight: 600;">展开预检机制与阻断契约</summary>
<div class="rule-content" style="padding-top: 0.4rem; font-size: 0.78rem; color: var(--text-secondary); line-height: 1.6;">
<div>· 时间倒挂阻断:startTime >= endTime 拦截返回 <code>40010</code>。</div>
<div>· 教室与场地同时为空或同时赋值强行拦截,DB 层面 <code>invalid venue XOR = 0</code>。</div>
<div>· 已结训计划禁止排课变更,返回 <code>40012: PLAN_LOCKED</code>。</div>
</div>
</details>
<div class="card-footer-action">
<span class="card-code">无 force 旁路安全参数</span>
<span class="badge badge-pass">验证通过</span>
</div>
</div>
<!-- SC-04 -->
<div class="scenario-card">
<div class="card-top">
<span class="badge badge-pass">SCENARIO-04 · PASS</span>
<span class="card-code">Electronic Board</span>
</div>
<h3 class="card-title">电子班牌绑定与课表降级映射</h3>
<ul class="scenario-point-list">
<li class="scenario-point-item"><strong>UUID 主键规范</strong>:班牌创建自动生成 32 位 UUID,默认在线状态 <code>0</code>。</li>
<li class="scenario-point-item"><strong>课表降级规则</strong>:0 块板为 null,恰好 1 块板返回对应 SN,多块板降级为 null。</li>
<li class="scenario-point-item"><strong>逻辑删除保障</strong>:调用删除置 <code>del_flag='2'</code>,业务接口隔离且物理数据保留。</li>
</ul>
<details class="rule-collapse" style="margin-top: 0.6rem;">
<summary style="font-size: 0.78rem; color: var(--rail-blue); cursor: pointer; font-weight: 600;">展开教室绑定与降级细节</summary>
<div class="rule-content" style="padding-top: 0.4rem; font-size: 0.78rem; color: var(--text-secondary); line-height: 1.6;">
<div>· 教室详情接口:0 块板返回空数组,1 块板返回长度 1,N 块板返回长度 N。</div>
<div>· 课表 boardSn 降级消除多设备展示歧义。</div>
<div>· 物理库 SQL 断言确认 <code>del_flag='2'</code> 匹配成功。</div>
</div>
</details>
<div class="card-footer-action">
<span class="card-code">板号降级语义实测</span>
<span class="badge badge-pass">验证通过</span>
</div>
</div>
<!-- SC-05 -->
<div class="scenario-card">
<div class="card-top">
<span class="badge badge-pass">SCENARIO-05 · PASS</span>
<span class="card-code">Plan Completion</span>
</div>
<h3 class="card-title">结训生命周期与退训学员保护</h3>
<ul class="scenario-point-list">
<li class="scenario-point-item"><strong>计划结训 API</strong>:调用结训接口,Plan 状态成功流转为 <code>2:已结束</code>。</li>
<li class="scenario-point-item"><strong>活动学员结训</strong>:待报到/已报到学员批量流转为 <code>4:已完成</code> 并记录时间。</li>
<li class="scenario-point-item"><strong>退训学员防覆盖</strong>:已退训学员 (<code>status=3</code>) 状态与完成时间受严格保护。</li>
</ul>
<details class="rule-collapse" style="margin-top: 0.6rem;">
<summary style="font-size: 0.78rem; color: var(--rail-blue); cursor: pointer; font-weight: 600;">展开核心不变量与事务保障</summary>
<div class="rule-content" style="padding-top: 0.4rem; font-size: 0.78rem; color: var(--text-secondary); line-height: 1.6;">
<div>· 接口契约:<code>POST /train/plans/{id}/complete</code> 强事务批量更新。</div>
<div>· 核心不变量:已退训记录 complete_time 严格保持为 null。</div>
<div>· 物理 SQL 断言证实历史退训记录零覆盖、零损坏。</div>
</div>
</details>
<div class="card-footer-action">
<span class="card-code">强事务一致性保护</span>
<span class="badge badge-pass">验证通过</span>
</div>
</div>
<!-- SC-06 -->
<div class="scenario-card">
<div class="card-top">
<span class="badge badge-pass">SCENARIO-06 · PASS</span>
<span class="card-code">Report & Attendance</span>
</div>
<h3 class="card-title">现场报到与课堂考勤完整闭环</h3>
<ul class="scenario-point-list">
<li class="scenario-point-item"><strong>现场报到确认</strong>:调用报到接口返回 reportId,参训状态流转为 <code>1:已报到</code>。</li>
<li class="scenario-point-item"><strong>考勤打卡幂等</strong>:首次记录打卡时间,重复签到签退严格保持初次时间不变。</li>
<li class="scenario-point-item"><strong>人工代签留痕</strong>:后台调整考勤自动置 <code>manual_flag = 1</code> 留存审计线索。</li>
</ul>
<details class="rule-collapse" style="margin-top: 0.6rem;">
<summary style="font-size: 0.78rem; color: var(--rail-blue); cursor: pointer; font-weight: 600;">展开接口与重复拦截细节</summary>
<div class="rule-content" style="padding-top: 0.4rem; font-size: 0.78rem; color: var(--text-secondary); line-height: 1.6;">
<div>· 报到确认接口:<code>POST /train/enrollments/{id}/report</code> 强事务写入。</div>
<div>· 重复报到防范:对已报到学员再次报到触发拦截返回 <code>40050</code>。</div>
<div>· 人工考勤接口:<code>PUT /train/attendance/{id}</code> 记录调整人与备注。</div>
</div>
</details>
<div class="card-footer-action">
<span class="card-code">幂等性与审计留痕</span>
<span class="badge badge-pass">验证通过</span>
</div>
</div>
</div>
</section>
<!-- 4. 浏览器端到端验收分类卡片 (真实客观实测事实) -->
<section class="content-section">
<div class="section-header">
<div>
<h2 class="section-title">真实浏览器端到端交互验收 (Final Browser Acceptance)</h2>
<p class="section-subtitle">基于真实运行的端到端验收套件客观记录,据实描述界面打开、列表查询、表单验证与接口调用结果。</p>
</div>
</div>
<div class="browser-category-grid">
<!-- 分类 1: 基础主数据 -->
<div class="browser-card">
<div class="card-top">
<h3 class="card-title" style="font-size: 0.95rem;">基础主数据管理</h3>
<span class="badge badge-pass">PASS</span>
</div>
<ul style="padding-left: 1.15rem; font-size: 0.82rem; line-height: 1.75; color: var(--text-secondary); margin-top: 0.5rem;">
<li><strong>Teacher 教师</strong>:页面成功打开、列表查询、手机号唯一性强校验生效。</li>
<li><strong>Student 学员</strong>:建档表单校验通过,身份证末4位脱敏掩码展示正常。</li>
<li><strong>Classroom 教室</strong>:理论教室容量与状态设置正常渲染。</li>
<li><strong>TrainingSite 场地</strong>:实训场地类型与容量管理正常。</li>
</ul>
</div>
<!-- 分类 2: 核心教务编排 -->
<div class="browser-card">
<div class="card-top">
<h3 class="card-title" style="font-size: 0.95rem;">核心教务编排交互</h3>
<span class="badge badge-pass">PASS</span>
</div>
<ul style="padding-left: 1.15rem; font-size: 0.82rem; line-height: 1.75; color: var(--text-secondary); margin-top: 0.5rem;">
<li><strong>Plan 培训计划</strong>:拟定 32 位 UUID 计划,生命周期初始为草稿。</li>
<li><strong>PlanCourse 课程绑定</strong>:课程选择与师资指派正常,总学时联动累加。</li>
<li><strong>Enrollment 学员报名</strong>:入班名单展示、招生容量上限校验拦截生效。</li>
<li><strong>Timetable 可视化排课</strong>:课表渲染,冲突 Warning 取消/确认入库均正常。</li>
</ul>
</div>
<!-- 分类 3: 现场执行与系统工具 -->
<div class="browser-card">
<div class="card-top">
<h3 class="card-title" style="font-size: 0.95rem;">现场执行与系统工具</h3>
<span class="badge badge-pass">PASS</span>
</div>
<ul style="padding-left: 1.15rem; font-size: 0.82rem; line-height: 1.75; color: var(--text-secondary); margin-top: 0.5rem;">
<li><strong>开班报到确认</strong>:前端菜单「开班签到」,实际调用 <code>/report</code> 接口 200 OK,状态由待报到流转为已报到。</li>
<li><strong>电子班牌终端</strong>:前端物联路由 <code>/equip/board</code> 页面正常渲染。</li>
<li><strong>统计分析 (Analytics)</strong>:报到、考勤、学时、画像报表只读查询正常。</li>
<li><strong>系统接口平台 (Swagger)</strong>:<code>系统工具 → 系统接口</code> 在线渲染,<code>v3/api-docs</code> 返回 200。</li>
</ul>
</div>
</div>
</section>
<!-- 5. 数据清理口径澄清 (严格区分两种测试) -->
<section class="content-section">
<div class="section-header">
<div>
<h2 class="section-title">测试数据清理与审计口径规范</h2>
<p class="section-subtitle">严格区分自动化集成测试与浏览器真实验收的数据保留策略,杜绝将两者混写。</p>
</div>
</div>
<div class="cleanup-box">
<div class="standard-card" style="border-left: 4px solid var(--pass-color);">
<div class="card-top">
<h3 class="card-title" style="font-size: 0.98rem;">自动化集成测试清理 (Integration Cleanup)</h3>
<span class="badge badge-pass">0 残留 (Residue = 0)</span>
</div>
<p class="card-body-text">
<code>core-business-scenario-runner.py</code> 在测试执行过程中自动记录所创建的精确 ID 资源清单 (<code>created-resources.json</code>)。测试断言结束后,严格按逆序调用解绑与清理机制,实现:
<br>· 课次、参训名单、电子班牌精确删除;
<br>· <strong>自动化测试产生的数据残留为 0,保持数据库纯净</strong>。
</p>
</div>
<div class="standard-card" style="border-left: 4px solid var(--pending-color);">
<div class="card-top">
<h3 class="card-title" style="font-size: 0.98rem;">浏览器端到端验收数据 (Browser Acceptance Data)</h3>
<span class="badge badge-pending">保留供审计 (KEEP_FOR_AUDIT)</span>
</div>
<p class="card-body-text">
在最终浏览器真实 UI 验收中创建及流转的测试学员、开班计划、报到流水及代签考勤记录,<strong>显式全量保留在数据库中</strong>,供发布审查、合规抽检及操作日志审计核对,<strong>非 0 残留</strong>。
</p>
</div>
</div>
</section>
<!-- 6. 技术细节折叠手风琴 (Kingbase SQL 物理断言) -->
<section class="content-section" style="margin-bottom: 0;">
<div class="section-header">
<div>
<h2 class="section-title">技术底层细节与物理 SQL 断言 (展开查阅)</h2>
<p class="section-subtitle">由 DbAssertionChecker 直接在 Kingbase 物理库上执行的只读一致性核查。</p>
</div>
</div>
<div class="accordion-item">
<div class="accordion-header">
<div class="accordion-title-group">
<span class="badge badge-pass">11 项 SQL 断言通过</span>
<span style="font-size: 0.95rem; font-weight: 600; color: var(--navy-900);">查看 Kingbase 物理库只读一致性断言清单</span>
</div>
<svg class="accordion-arrow" viewBox="0 0 20 20"><path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/></svg>
</div>
<div class="accordion-body">
<div class="data-table-wrap">
<table class="data-table">
<thead>
<tr>
<th>断言项目</th>
<th>验证逻辑与 SQL 条件</th>
<th>预期值</th>
<th>实测结果</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>duplicate enrollment</strong></td>
<td>校验同一计划内是否存在相同学员的重复报名记录</td>
<td><code>0</code></td>
<td><span class="badge badge-pass">PASS (0)</span></td>
</tr>
<tr>
<td><strong>orphan lesson</strong></td>
<td>校验是否存在未关联有效计划课程的孤立课次</td>
<td><code>0</code></td>
<td><span class="badge badge-pass">PASS (0)</span></td>
</tr>
<tr>
<td><strong>orphan enrollment</strong></td>
<td>校验是否存在未关联有效培训计划的孤立报名档案</td>
<td><code>0</code></td>
<td><span class="badge badge-pass">PASS (0)</span></td>
</tr>
<tr>
<td><strong>invalid venue XOR</strong></td>
<td>校验课次中教室与实训场地二选一互斥,严禁同时为空或同时设置</td>
<td><code>0</code></td>
<td><span class="badge badge-pass">PASS (0)</span></td>
</tr>
<tr>
<td><strong>del_flag = '2'</strong></td>
<td>电子班牌逻辑删除后底层标志位规范设置为 '2'</td>
<td><code>MATCH</code></td>
<td><span class="badge badge-pass">PASS</span></td>
</tr>
<tr>
<td><strong>Plan completion 状态联动</strong></td>
<td>结训后学员参训状态正确流转至 4 且 complete_time 真实写入</td>
<td><code>status=4, time!=null</code></td>
<td><span class="badge badge-pass">PASS</span></td>
</tr>
<tr>
<td><strong>历史退训保护</strong></td>
<td>已退训记录未被结训批量任务修改覆盖,complete_time 保持为 null</td>
<td><code>status=3, time=null</code></td>
<td><span class="badge badge-pass">PASS</span></td>
</tr>
<tr>
<td><strong>orphan report</strong></td>
<td>校验是否存在未关联有效报名记录的孤立报到流水</td>
<td><code>0</code></td>
<td><span class="badge badge-pass">PASS (0)</span></td>
</tr>
<tr>
<td><strong>orphan checkin</strong></td>
<td>校验是否存在未关联有效课次或报名的孤立考勤打卡</td>
<td><code>0</code></td>
<td><span class="badge badge-pass">PASS (0)</span></td>
</tr>
<tr>
<td><strong>manual_flag = 1</strong></td>
<td>人工修正考勤记录自动置审计标志位 manual_flag = 1</td>
<td><code>1</code></td>
<td><span class="badge badge-pass">PASS (1)</span></td>
</tr>
<tr>
<td><strong>report enrollment status</strong></td>
<td>报到流水关联的报名记录状态必须为 1(已报到) 或 4(已结训完成)</td>
<td><code>status IN (1, 4)</code></td>
<td><span class="badge badge-pass">PASS</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
</main>
<!-- 页脚 -->
<footer class="site-footer">
<p>RailwaySystem SYFJGYD学员管理系统 · 共享试用就绪 (Shared Trial Ready)</p>
<p style="margin-top: 0.35rem; color: var(--text-muted);">
质量门禁与测试报告 · <a href="../reports/integrated-acceptance/FINAL_PHASE1_20260917_101044/build-test-final.md">查阅综合测试终检报告</a> · <a href="../reports/integrated-acceptance/FINAL_PHASE1_20260917_101044/browser-e2e-final.md">查阅浏览器端到端报告</a>
</p>
</footer>
<script src="assets/docs.js"></script>
</body>
</html>