【听见课堂 HarmonyOS NEXT 实战系列 20】删除、重启、恢复:如何验证本地数据库不是“看起来能用”
本地数据库最容易产生一种错觉:页面保存后能看到新内容,就认为持久化已经完成;点击删除后列表消失,就认为数据已经彻底清理。实际上,页面可能只更新了内存数组,重启后旧数据会重新出现;删除可能只清掉父记录,字幕、板书和任务仍留在子表;演示 Seed 还可能在下一次启动时把“被删除的数据”悄悄填回来。
听见课堂为此执行过一轮 TASK-007 破坏性数据流程验收:先保存可恢复数据库基线,再分别验证 P03 单课程级联删除和 P11 全量课堂数据删除,检查业务表数量、页面空态、seed_initialized、设置偏好、强停前后 PID 和恢复后的再次重启。本文不只复述结果,更重要的是拆解如何设计一套不会靠肉眼自我安慰的 RelationalStore 持久化测试。

一、持久化验收必须回答四个不同问题
“数据库能用”至少包含四种能力:
- 写入后,进程结束再启动仍能回读;
- 删除后,进程结束再启动仍保持删除结果;
- 失败时,事务不会留下半套数据;
- 测试结束后,可以恢复约定的可用基线。
只做第一项会漏掉删除和迁移问题,只做 UI 截图又无法证明数据库状态。有效验收需要同时收集数据层、进程层和界面层证据。
二、先定义业务表、元数据和偏好的边界
当前课堂域包含四张业务表:courses、transcript_segments、scan_notes 和 tasks。另外还有 schema_meta 保存 schema_version 与 seed_initialized。
显示偏好位于 UserPreferences,包括主题、字幕字号、高对比和最近页面等,它们不属于课堂业务表。系统麦克风、相机权限则由系统管理,也不应被本地课堂数据删除动作改变。
所以 P11“清空全部课堂数据”的后置条件不是“应用里什么都没有”,而是:四张业务表清空,schema 元数据保留,设置偏好保留,系统权限不被修改。
三、破坏性测试前必须准备可恢复基线
TASK-007 在执行永久删除前保存了一份 SQLite 一致性备份,并记录当时基线:
| 项目 | 当时验收值 |
|---|---|
| 课程 | 1 |
| 字幕 | 4 |
| 板书扫描 | 2 |
| 任务 | 3 |
| schema | v1 |
seed_initialized |
1 |
这些是 2026-07-13 那轮验收的历史值。当前源码已经迁移到 schema v2,因此今天重新测试时应重新读取当前值,不能直接复制旧表格作为新证据。
可恢复基线至少要记录数据库文件大小或哈希、表行数、元数据值、应用包版本、设备标识和页面状态。没有恢复方案就执行永久删除,会把验收变成数据事故。
四、P03 删除课程与 P11 清空全部数据要分开测
两条路径在单课程 Demo 中可能都得到 0/0/0/0,但产品语义不同。
- P03 从课程编辑上下文删除目标
courseId,只处理该课程聚合; - P11 从设置与隐私的数据管理入口清空所有课堂域数据;
- P03 应验证目标条件和跨表级联;
- P11 应验证精确影响范围、设置偏好保留与全局空态;
- 两者都需要二次确认、取消路径和失败提示。
如果只测其中一条,另一条即使复用了 Repository 代码,也不能自动算作运行通过。
五、事务让四张表只有“全空”或“全保留”
当前 P11 的 clearAllData() 在一个 RelationalStore 事务中按任务、扫描、字幕、课程顺序清理:
async clearAllData(): Promise<boolean> {
const store: relationalStore.RdbStore = this.getStore();
store.beginTransaction();
try {
await store.executeSql(`DELETE FROM ${TABLE_TASKS}`);
await store.executeSql(`DELETE FROM ${TABLE_SCANS}`);
await store.executeSql(`DELETE FROM ${TABLE_TRANSCRIPT}`);
await store.executeSql(`DELETE FROM ${TABLE_COURSES}`);
store.commit();
return true;
} catch (error) {
store.rollBack();
throw new Error('Failed to clear classroom data.');
}
}
正常路径能证明提交后的结果,不能单独证明回滚。若要覆盖事务原子性,还要通过可控故障让中间某一步抛错,再回读四张表确认全部保持删除前状态。
六、seed_initialized 决定“首次安装”与“用户清空”
数据库初始化时,项目先读取 seed_initialized。只有该标记为 0 且课程表为空,才插入演示基线;完成后把标记写为 1。
因此用户主动删除课堂数据后,四张业务表可以为空,但 seed_initialized 仍然保持 1。下一次启动看到空库时,初始化逻辑知道这是“已经初始化后被清空”,不会重新插入演示内容。
这条元数据是删除语义的一部分。如果全量删除顺手清掉 schema_meta,应用重启后可能误判为首次安装,让旧演示数据复活,页面看起来像“删除失败”。
七、删除成功要同时看数据库和页面空态
TASK-007 的 P03 删除后,课程、字幕、板书和任务从 1/4/2/3 变成 0/0/0/0。页面同时出现“还没有课程”“本机数据已清理”和创建课程入口。
数据库为空但页面仍显示旧卡片,说明 canonical data 没有重新读取;页面为空但数据库仍有记录,说明 UI 只是手工清空。两者必须同时通过。
验收还发现过一个真实缺陷:重新创建空课程后,数据库字幕数为 0,首页却显示“3 条重点已标记”。根因是页面文案硬编码,修复后改为从 TranscriptSegment.isKeyPoint 动态计算。这个案例说明破坏性流程会暴露平常有演示数据时看不出的假统计。
八、强停和换 PID 才能排除页面内存假象
普通返回首页不等于进程重启。页面组件、Service 单例或内存 Repository 可能仍然保存着旧对象。TASK-007 在删除后强停应用,记录旧 PID,再重新启动并确认 PID 变化。
P03 删除后的证据记录了 23085 → 27608,重启后业务表仍为 0/0/0/0;重新创建课程后再次强停,PID 5363 → 5403,课程仍存在且关联数据仍为 0。P11 删除后也执行了同类检查。
PID 只是“进程确实换了”的证据,不是持久化结果本身。还需要重启后重新查询数据库并检查页面,三者结合才能排除内存残留。

九、设置偏好保留要用可识别标记证明
P11 全量删除前,测试把字幕字号改为“大号”作为偏好标记。确认永久删除后,四张课堂业务表清空;强停重启后,字幕字号仍为大号。
这个步骤证明课堂数据与设置偏好处在不同的持久化域。只检查“主题看起来没变”不够,因为默认主题可能恰好与删除前相同;选择一个非默认、可读回的标记更有辨识度。
测试结束后又把偏好恢复为标准,避免把验收痕迹留给后续运行。测试准备和测试收尾同样属于交付质量。
十、恢复数据库基线不等于产品已有导入能力
TASK-007 使用预先保存的数据库备份恢复演示基线,最终重新得到 1/4/2/3,并再次重启确认数据仍在。这证明测试环境能够回到约定状态。
但这不是应用内的“从备份恢复”功能。工程人员通过设备文件和数据库备份恢复,与普通用户通过 UI 选择 JSON、校验版本、处理冲突并事务导入,是两套完全不同的能力。
技术文章必须把“验收恢复手段”和“产品恢复能力”分开。否则读者会误以为第19篇的 JSON 已经可以直接导入,而当前项目并没有实现这条链路。
十一、UI 树、截图和数据库查询分别证明什么
一套可审计证据应该回答“数据是什么、用户看到什么、动作发生在哪个进程”。
| 证据 | 能证明 | 不能单独证明 |
|---|---|---|
| 数据库行数与元数据 | 表的真实状态、schema 与 Seed 标记 | 页面已经刷新 |
| UI 树 | 文案、按钮、空态和控件结构 | 像素视觉完全正确 |
| 截图 | 用户可见布局和提示 | 数据库真的持久化 |
| PID | 进程是否变化 | 重启后读到了正确数据 |
| 构建与安装日志 | 当前代码可编译、包可安装 | 删除流程行为正确 |
| 崩溃扫描 | 未发现目标时间段崩溃签名 | 所有业务断言通过 |
TASK-007 同时保存 .jpeg 与 .json 页面证据,并记录数据库、PID、构建、安装和崩溃扫描结果。这样的证据链比一张“删除成功”Toast 截图可靠得多。
十二、恢复测试要防止“Seed 假恢复”
恢复后重新看到一门课程,可能有两种完全不同的原因:
- 备份确实恢复成功;
- 恢复失败,但初始化逻辑重新 Seed 了同样的演示课程。
为了区分,需要检查 seed_initialized、记录恢复动作、比对特征字段,并再次重启。更严格的做法是为基线记录哈希或使用带验收标记的非默认课程标题,确认恢复内容来自备份而不是初始化代码。
当前项目旧验收同时记录了元数据、行数和恢复后的再次重启,降低了“看起来恢复”的误判;哈希级数据内容比对仍可以继续加强。
十三、schema v1 历史证据如何迁移到当前 v2 语境
TASK-007 报告发生在 schema v1 阶段,当前 Repository 的 SCHEMA_VERSION 已经是 2。v2 增加课程 color_token 和任务 due_at_ms,并在 migrateV1ToV2() 中执行字段添加与旧任务截止时间回填。
因此今天复跑破坏性测试时,需要新增三项检查:
- 删除前确认数据库已经迁移到 v2;
- 删除后确认
schema_version=2与seed_initialized=1保留; - 恢复旧 v1 备份时先走受控迁移,再验证新字段默认值和任务时间。
旧报告仍然是有效的历史运行证据,但不能证明当前 v2 的升级恢复、设备重启或失败注入已经通过。
十四、失败注入是当前证据链的主要缺口
正常删除已验证事务提交和重启不回填,但没有让第二或第三条 SQL 人为失败。没有这一步,就不能把 rollBack() 的源码存在等同于运行时回滚证明。
可测试的 Repository 可以注入一个受控故障点:
before_delete_tasks → 抛出测试异常
预期:courses/transcript/scans/tasks 全部保持原计数
页面:显示删除失败,不导航到空态
重启:仍能回读完整基线
故障开关只能存在于测试实现或受控构建,不能暴露给正式用户。失败日志也不应打印字幕、OCR 正文、教师姓名或任务内容。
十五、设备与场景覆盖要明确写出未验证项
旧验收在 HarmonyOS 6.0.2(22) phone 模拟器上完成,证明了该环境中的 P03/P11 流程。报告当时仍把以下项目列为 not run:独立 tablet/2in1 删除流程、真机文件系统、模拟器整机重启、应用升级迁移、1.5 倍系统字体、真实读屏和长课堂性能。
后来项目做过其他 2in1 与真机专项,不代表破坏性删除矩阵自动补齐。验证结论必须绑定具体动作、版本、设备和时间,不能用“项目已经上过真机”覆盖未运行的删除测试。
复跑前还应先执行设备预检,确认目标在线且 /data/local/tmp 可写;若设备离线,应把运行项标记为 not run,不能用构建通过代替。
十六、总结与可执行验收清单
验证本地数据库的关键,是主动破坏“页面看起来正常”的舒适区。听见课堂通过可恢复基线、两类删除入口、事务清理、Seed 门禁、数据库计数、UI 空态、强停换 PID、偏好标记和最终恢复,建立了一条可复核的持久化证据链。
执行时可以按以下清单收口:
- 记录应用版本、设备、数据库 schema 和业务表基线;
- 破坏性操作前保存可验证、可删除的测试备份;
- P03 单课程删除与 P11 全量清理分别验收;
- 第一次点击只进入二次确认,取消后数据不变;
- 确认后四类业务数据满足目标后置条件;
-
schema_version和seed_initialized按约定保留; - 页面空态、统计、任务中心和历史视图一起刷新;
- 强停前后 PID 不同,重启回读与数据库一致;
- 使用非默认偏好标记证明设置域被保留;
- 恢复结果与自动 Seed 能被区分;
- 失败注入证明事务回滚,而不只读源码;
- 恢复后再次重启,确认不是内存假象;
- 清理测试备份和临时设备文件,恢复交付基线;
- v1 历史证据与当前 v2 新验证分开记录;
- 模拟器、真机、tablet/2in1、升级迁移和长数据量分别标注
passed / failed / not run; - 不把工程数据库恢复包装成已实现的用户导入功能。
至此,系列二完成了从 Repository 接口、RelationalStore schema、迁移、Seed、事务、级联删除、canonical data、JSON 导出到破坏性持久化验收的完整闭环。下一篇将进入 Core Speech Kit 实时字幕,先从会话状态机而不是模型 API 开始设计。
更多推荐


所有评论(0)