rakullapp_core)的测试分为两层 —— 表单测试与功能测试 —— 以「按平台、按功能」拆分的用例清单形式编目,并用一个自带的 HTML 看板来跟踪。本页与偏后端的测试页互补。
两层测试
表单测试 (Form)
只测单个 widget 或函数本身,不启动任何服务器。喂给它假数据(用
http 的 MockClient),看用户最终看到什么:布局;加载 / 空 / 出错三种状态;输入校验;页面跳转;以及 JSON 解析。跑得快,用普通的 flutter test 就行。目前所有自动化客户端测试都是这一类。功能测试 (Func)
对着一台真实在跑的服务器测 —— 只有真服务器才能验证的东西:完整登录、数据是否真的存下来了、「不能动别人的数据」这类权限规则、实时进度推送(SSE),以及前后端对请求/返回格式是否一致。
unit_test/(相当于我们的表单测试)和 functional_test/。SSE(Server-Sent Events)是一条服务器到客户端的单向流,在长任务运行时实时推送进度(比如「处理中 40%」)。
功能测试能自动化吗?
可以 —— 有三种方式,由低成本到高成本:- 接口级(最省) —— 用真实的
http.Client()构造StudyApi/UserApi/CollectionApi,打到本地服务栈(scripts/run_all_servers.sh)。无需模拟器;与rakull_server自己的functional_test/思路一致。用@Tags(['functional'])做开关。 - 完整 E2E —— 引入
integration_test包,在 CI 里无头运行(flutter test integration_test),通过--dart-define把各服务地址指向本地栈。 - 种子数据 —— 用
scripts/init_all_dbs.sh+ 一个 root/邀请码种子准备确定性数据;各服务共享同一个JWT_SECRET。
仍需手动 / 只能部分自动化: Google 与 Supabase 的 OAuth 授权(交互式)、音频播放(
audioplayers)与文件选择(file_picker)插件,以及 web 上的 SSE(只有最终事件)。各平台清单里都已显式标注。用例清单目录
用例位于rakullapp_core/test/test_cases/,先按平台、再按功能组织:
| 平台 | 关注点 |
|---|---|
web | BrowserClient 的 SSE(仅最终事件)、Supabase OAuth web 回退、打字机标题 |
mobile | Android + iOS · 原生 Google 登录、设备文件选择、音频播放 |
desktop | macOS + Windows + Linux · 增量 SSE、桌面 audioplayers、鼠标选词 |
| 功能 | 涉及的页面 / 组件 |
|---|---|
auth | begining、login、connect-apps、RegisterService、SupabaseBootstrap、UserDbSyncService |
study-login | study_login_screen、UserApi 鉴权 |
library | dashboard、article_list、体裁筛选 |
reader | reader_screen、讲解/diff/编辑面板、逐句音频、收藏选择器 |
upload | article_upload、video_upload、task_progress_dialog(SSE) |
collections | collections_screen、collection_picker、CollectionApi |
admin | admin_screen(用户 / 邀请码 / 申请 / 文章) |
怎么读一条清单
每行末尾都有一个方括号标签,例如— [Form · Widget+Mock · (DI: SseClient)],从左往右读:
- 第一个词 ——
Form或Func—— 属于哪一层(见上)。它也决定这一行算不算一条真正的测试;没有这个标签的行只是一条灰色备注。 - 中间 —— 这条测试实际怎么写:
| 标签 | 含义 |
|---|---|
Pure | 纯 Dart 单元测试,不涉及 UI |
Widget | 渲染这个 widget,检查显示是否正确 |
Widget+Mock | 渲染 widget,背后接一个假的 API(MockClient) |
Widget+prefs | 在 widget 测试里顺带设置 SharedPreferences |
MockClient | 用假的 HTTP 客户端测试某个 API 类 |
Live API | 打到真实在跑的服务器 |
integration_test | 完整的端到端运行 |
Manual / Manual/device | 由人在真实浏览器 / 设备上手动检查 |
(DI: …)—— 现在还测不了。 这个 widget 在内部自己 new 了依赖,测试没法换成假的;得先改成「可注入」(见可测性阻塞项)。所谓可注入,就是让 widget 通过构造函数接收它的 API / 服务,而不是自己在内部创建 —— 这样测试就能传一个假的进去。
可视化跟踪器
我们没有引入托管的测试管理工具,而是直接由清单生成一个自带的单文件 HTML 看板 —— 无需账号、无需服务器、无需联网。用浏览器打开它逐项打勾即可,进度会保存在浏览器的localStorage 里。
逐项跟踪
每项可标记 Pass / Fail / Block / N/A(再次点击取消),并可展开备注记录缺陷链接或环境。
查看进度
实时的堆叠进度条 + 每张卡片的
x/y done;运行标签(如 v0.4 · macOS)用于命名本轮测试。筛选
按文本/标签搜索,并可按平台、功能、层(Form/Func)、状态、仅 DI 阻塞项、隐藏已完成过滤。
共享一轮结果
导出 / 导入 JSON 用于备份或在多台机器间迁移,Copy summary 生成 markdown 的通过/失败报告。
markdown 清单始终是唯一事实来源。编辑清单后重新运行
python3 test/test_cases/build_tracker.py 即可 —— 已保存的状态以 (平台, 功能, 文本) 的稳定哈希为键,因此重排顺序或改标签都不会丢;只有改动某一项的文本才会重置该项。增 / 改 / 删一个用例
用例就是普通 markdown。编辑对应的test/test_cases/<platform>/<function>/checklist.md,然后重新生成跟踪器:
-
新增 —— 在正确的标题下(
## Form tests、## Functional tests或## Platform-specific)加一行:第一个标签必须是Form或Func—— 这是它被当作可跟踪测试的依据;没有该标签的行会渲染成灰色备注,且不计入进度。组件需要先改造成可注入时,补上(DI: X)。 -
修改 —— 直接改这一行。只改标签(如
Form→Func、加(DI))会保留已保存的状态;改文本会生成新 id,于是该项重置为未测。 -
删除 —— 删掉这一行。它原来的状态会无害地残留在浏览器的
localStorage里;忽略即可,或用跟踪器的 Reset。 -
新增功能或平台 —— 新建
…/<platform>/<function>/checklist.md。生成器会自动发现任意*/*/checklist.md,无需改代码即可识别;为排序好看,把名字加到build_tracker.py的PLATFORM_ORDER/FUNCTION_ORDER,并补到上面的目录表里。
运行测试
表单层(快,不需要服务器):integration_test 包):
可测性阻塞项
下列组件自己 new 了依赖,因此暂时无法用 mock 做表单测试。加上构造函数注入(就像ArticleListView、ReaderScreen 和各 *Api 类已经做的那样)即可解锁清单里的 (DI) 项。
| 组件 | 阻塞点 | 修法 |
|---|---|---|
LoginScreen / ConnectAppsScreen | 在 State 里 new RegisterService() | 增加可选的 registerService 参数 |
DashboardScreen | 内部创建 StudyApi/UserApi/AuthStore | 通过构造函数注入这三个客户端 |
StudyLoginScreen | 在 State 里 new UserApi() | 增加可选的 userApi 参数 |
AdminScreen 各 tab | 每个 tab 各自创建 UserApi/StudyApi | 向各 tab 注入共享客户端 |
TaskProgressDialog | 在 State 里 new SseClient() | 给 showTaskProgress 增加可选的 sseClient 参数 |
VideoUploadScreen | FilePicker.platform 静态单例 | 用可注入接口封装文件选择 |
SentenceAudioButton | 在 State 里 new AudioPlayer() | 用可注入接口封装播放 |
RegisterService / SupabaseBootstrap | 顶层 http.post/http.get | 接受 http.Client,让 finalize/config 调用可被 mock |