分层方法论 + 脚手架模板 + 单一编排入口 · 可自动化 / 可量化 / 可自定义
make test / test.sh)按 profile 跑对应层,输出一份合并报告。前不久的「mac 实时统计恒为 0」就是典型反例:原生侧产出统计、Dart 侧消费统计,两端的 pangolin/vpn/stats 契约没有任何自动化测试守门,触发条件一变(隧道已连接才启动 app)就静默失效。L0 契约层防的正是这类 bug。
图 1 · 五层金字塔:L0 契约为地基,L1→L4 自底向上
统计/记账数据沿一条跨进程链路流动。每段接缝标注其契约,以及哪一层测试在此下刀——这张图就是「该测什么、在哪测」的地图。
图 2 · 契约接缝:从节点数据面一路回到客户端 UI
实时上下行走 ①(libbox→EventChannel→Dart);累计流量/周柱走 ④→③→DB→②(节点统计→agent→控制面入库→客户端拉取)。两条流共用同一张接缝图,测试各自归位。
把你提出的四类测试诉求逐条落到层、工具、量化指标与现状:
| 诉求 | 层 | 工具 | 量化指标 | 现状 |
|---|---|---|---|---|
| ① 界面设计一致 每端一致 |
L3 | Flutter golden(字体锁定,host 无关→天然每端一致);design-distill pixelmatch 对照 HTML 原型真相源 | 像素不一致比例 vs 阈值 0.05;golden 通过/失败 |
素材齐 · CI 没跑 |
| ② 前端交互 连接 / 登陆 / 登出 |
L2 | flutter_test 进程内驱动整树 + VpnBridgeMock + http MockClient + ProviderScope 覆盖 | 流程通过/失败;覆盖率 lcov | 素材齐 · 待组装 |
| ③ 数值型 统计 / 实时速度 / 时延 |
L1 + L4 | L1:格式化/换算纯逻辑单测;L4:连真节点拉 stats 断言 bytes 增长、延迟 >0 | 实测 vs 期望容差;时延 ms;速率 B/s >0 | L1 可补 L4 缺 |
| ④ 后端网络 按租户流量 / 限流 |
L2 + L4 | L2:SQLite 真库 + testcontainers MySQL(已覆盖并发/重放/跨日/配额);L4:全链路真流量计数 | 记账数值断言;限流是否触发;覆盖率 go cover | L2 强 L4 缺 |
pangolin/vpn/stats EventChannel 字段、gRPC UsageEntry proto、/v1/me·/v1/usage JSON 形态;静态:analyze/vet、OpenAPI 结构、红线词。ci/scan-redline.sh。flutter_test;go test + testify。go test -cover)。pumpWidget(真 App 树) 驱动 登陆→重启续登→点连接→状态流转→登出,把原生隧道换 VpnBridgeMock、服务端换 MockClient,ProviderScope 覆盖注入。httptest.Server(鉴权路径需补);gRPC bufconn + 自签 CA/CRL(已强)。flutter_test_config.dart 字体锁定→host 无关,所以「每端效果一致」天然成立);design-distill shoot-prototype.mjs + diff.mjs(pixelmatch)。0.05。golden 阻塞守门;权威环境脚本 scripts/update-goldens.sh。tablet/desktop-stats golden 待并入——它们与 stats-overhaul dirty 工作区耦合(基线/测试/被测 widget 同在 dirty),待其合并后跑 update-goldens.sh 生成 Linux 基线并加进 golden job。scripts/e2e-smoke.sh + server/test/e2e/——进程级:真 server 二进制 + sqlite + 内嵌 miniredis + gRPC enroll/ReportUsage → /v1/usage 断言统计真入库真读出(CI job e2e-smoke)。真 sing-box 出网流量留后续。一个入口按 profile 分流:CI 每 push 只跑快而稳的 fast;视觉与 E2E 按需触发。
图 4 · 单一入口 → 三 profile 泳道
图 5 · docker-compose 真链路 + 测试驱动器(推荐边界 a)
| 维度 | 指标 | 工具 / 口径 |
|---|---|---|
| 覆盖率 | 行/分支 % | flutter test --coverage + go test -coverprofile → 合并 lcov |
| 视觉 | 不一致像素比例 vs 阈值 0.05 | pixelmatch(diff.mjs);golden 通过/失败 |
| 数值精度 | 实测 vs 期望容差 | L4 断言 bytes/速率/累计落在容差带内 |
| 时延 | 毫秒 ms | urltest 延迟 >0 且在合理区间 |
| 稳定性 | 通过率 / flaky 率 | 多次重跑统计;flaky 用例隔离 |
跨项目模板(复制即用,按接缝微调):
VpnBridge 接口 + Mock)→ 任何「原生/外部依赖」都可照此切假。flutter_test_config.dart)→ 视觉回归通用骨架。run_sqlite_test.sh)+ testcontainers 集成套 → 任何 DB 项目通用。每项目按接缝定制:契约字段、API 路径、compose 服务清单、golden 屏清单、数值容差阈值。
| 序 | 动作 | 性价比 | 起点 |
|---|---|---|---|
| 1 | 补 L0 契约快照 + 把现有 golden 接进 CI | 最高(防回归) | 素材已有,组装即可 |
| 2 | 组装 L2 前端 flow 测试(登陆/续登/连接/登出) | 高 | Mock/覆盖素材全齐 |
| 3 | 建 L4 后端全链路冒烟 (a) | 中(覆盖"真不真") | 需新建 compose + 驱动器 |
| 4 | 补 L1 数值单测 + L2 HTTP 鉴权测试 | 中 | 增量补齐 |
| 5 | (b) desktop 真连 / (c) 真机——本地按需 | 低(贵·脆) | 需常驻 Mac / 真机 |
本文档仅为架构说明(设计真相源)。具体落地拆解(契约快照实现、前端 flow 用例、L4 冒烟脚本)走各自实现计划(writing-plans)。
| 缺口 | 严重度 | 现状 & 为什么没测 | 何时 / 怎么补 |
|---|---|---|---|
| 前端统计「上屏数值对不对」 | 中·诉求半覆盖 | 最初诉求是「统计数据对不对」。后端记账已 L4 e2e(ReportUsage → /v1/usage 断言字节);前端只到单测(device_usage_test/format_test 解析+格式化)。解析对 ≠ 渲染对——没有测试验证统计页拿到真实响应后上屏的数字/曲线是对的。 |
可立即补:widget 级测试,用 e2e server 的真实 /v1/usage 响应喂 stats_page,断言关键数值/曲线上屏。闭合诉求最后一环。 |
| 真实流量数据路径 | 高·最大盲区 | L4 e2e 是注入假 ReportUsage,不是「真 sing-box 出网 → v2ray stats 采集 → 上报记账」。真链路从未自动跑过——这是「我们其实不知道线上准不准」的根盲区。真出网那环仅 usage_v2ray.go 单测覆盖采集解析。 |
贵且脆(单机 docker 真出网重、CI 跑不动)。留后续按需,或本地手动跑一次真连基准。 |
| 原生隧道层 | 高 | 真正承载流量的代码——client/ios/Runner/VpnManager.swift、client/android/…/PangolinVpnService.kt、macOS 系统扩展——零自动化测试,全靠真机/模拟器手测。 |
依赖真机/模拟器,难纯 CI 化。至少补「连上→可达性探针→断开」的半自动脚本。 |
| 契约双份可能各自漂移 | 中 | Dart 与 Go 各冻一份字段面快照,两边是各自独立的真相,理论上可各自漂移(OpenAPI 改了、只更了一边)。 | 真·单源(从 OpenAPI 生成两端契约)是大改,列入 C 类效率项后续做。 |
| 缺口 | 严重度 | 现状 & 为什么 | 何时 / 怎么补 |
|---|---|---|---|
| CI 是单点 | 中·运维 | 整条 CI 吊在一台 mac(mac-pangolin-2)+ 依赖 jiu 的 relay + Docker Desktop。launchd 持久化尚未激活(见 docs/ci-runner.md 的一次性 ! 命令)——mac 一关/重启 CI 就全停。 |
激活持久化(脚本已就位);理想归宿是 NAS Linux host runner。 |
| go-integration job 脆性 | 中 | 跑在裸宿主机(go1.26.1,非容器,testcontainers 要真 docker,DooD 在 Docker Desktop mac 网络不通)——宿主 go 版本漂移即挂;套件 ~5min;曾踩 ryuk 关闭→容器泄漏坑(已用默认 ryuk + -p 1 串行规避)。 |
迁 NAS Linux host runner 可恢复容器化 hermetic;保持 ryuk 开启。 |
| analyze 严格化(闸·B1) | 中·阻塞 | 去掉 --no-fatal-infos 让 info 变致命,会因 HEAD stats_page.dart 的 withOpacity info 当场红;而清 info 正动 stats-overhaul 在改的同一文件。 |
被 stats-overhaul 耦合阻塞,其合并后立刻做。 |
| golden 全集(闸·B2) | 中·阻塞 | golden job 现只跑 components + auth;tablet/desktop-stats golden 与 tablet_pages_golden_test.dart 全是 dirty WIP,并入等于钉死未定稿基线。 |
stats-overhaul 合并后并入;连带补回临时摘除的 DeviceUsage 契约快照(Dart+Go)+ e2e /v1/usage/devices 断言。 |
| golden 本地难重生 | 低·摩擦 | Linux 权威基线(mac 渲染不一致),开发机改 UI 后须 docker 起 flutter 容器重生基线,非纯本地。 | scripts/update-goldens.sh 已封装,文档化即可,非真缺口。 |
维护约定:补上某项缺口后,从本表删去对应行(或标 已补);新发现的盲区即时登记到此,使本节始终等于「当前真实缺口」。