「怎么写才好测」——开发规范是可测试性的前置条件 · 与测试框架咬合
写法采用通用原则 + Pangolin 实例双层——原则可复制到任何多端项目,实例替换即可。
new 或调静态单例。分层单向依赖(token→实现→组件/页面),生成物 *.gen.dart 勿手改。VpnBridge 接口 → 可换 VpnBridgeMock;AuthApi(baseUrl, http.Client) 可注入。❌ NETunnelProviderManager 直接散在 UI / 业务里 new。pangolin/vpn/stats EventChannel、HTTP JSON、gRPC UsageEntry proto、设计 token)有唯一定义源,两端从它生成或校验,禁止两端各写各的。colors_and_type.css→*.gen.dart、proto→桩,生成物与源一致)。bytes→速率/时长 格式化、配额计算、时间 Go 端算好传 ?(不用 MySQL NOW())、golden 字体锁定。catch {} / 裸吞;错误类型可枚举。AuthApiException(statusCode, messageZh, messageEn)——带码、带双语、可断言。/healthz、/v1/usage),让测试读数值断言,而非截屏猜。VpnStatusStreamHandler / stats EventChannel / /v1/usage 数值出口。每条支柱直接解锁测试架构里的某些层。这张图就是规范与测试的咬合证明:
图 1 · 五支柱解锁五测试层(● 主要解锁 ○ 间接帮助)
读法:补齐支柱 2(契约单源)就点亮 L0;做好支柱 1 才有 L2 的假桥;支柱 5 是 L4 端到端能断言的唯一前提。规范不是道德要求,是测试能力的开关。
规范的说服力来自真实的坑。下表把项目里遇到/潜在的问题,回溯到被违反的支柱:
| 现象 / 真实 bug | 根因(违反了什么) | 本可由哪条支柱防住 |
|---|---|---|
| mac 实时统计恒为 0 | 原生侧产出、Dart 侧消费 stats,两端各写、无单源;触发条件一变即静默失效,无任何测试守门。 |
支柱 2 契约单源 + 支柱 5 可观测 |
| 延迟显示 stale / 拿不到 Android·Windows urltest |
延迟值没有稳定出口、没有契约校验,靠各端自行解析、刷新时机不一致。 | 支柱 5 可观测 + 支柱 2 契约单源 |
| 记账若用 MySQL 专属时间函数 | 时间/方言混进 SQL,无法在 SQLite 真库测、不可移植。 | 支柱 3 纯逻辑/IO 分离 时间 Go 端算、方言走 dialect |
| 连接交互测不了 (通用风险) |
连接逻辑直接依赖 NETunnelProviderManager / 真子进程,不连真隧道就无法驱动。 |
支柱 1 接缝即接口 藏到 VpnBridge 后面 |
| 失败路径不可测 / UI 卡死 (通用风险) |
连接失败只打日志不返回、空 catch 吞异常,调用方拿不到可断言的错误。 |
支柱 4 错误是有类型的值 |
规范只靠文档一定漂。执行 = 尽量把每条往机器自动守门推,推不动的用脚手架降低犯错成本 + 评审清单兜底。这不是一个动作,是从「写之前」卡到「push」的四道闸——越早拦,成本越低。
图 2 · 四道闸:从「写之前」预防到「push」硬闸,外加评审兜底
flutter analyze + go vet + 自定义规则(空 catch、import 方向、业务层禁直接 new 外部依赖),编辑器当场红线。*.gen.dart)。诚实区分「能不能机器强制」——能强制的硬卡 CI,强制不了的靠脚手架 + 评审兜底:
| 支柱 | 主执行手段 | 强制度 | 现状 |
|---|---|---|---|
| 1 接缝即接口 | import 方向 lint + 脚手架 + 评审清单 | 半自动 | 待补 |
| 2 契约单源 | 契约快照 diff=0 + codegen 漂移检查 | 强制 | 待补(OpenAPI 已有) |
| 3 纯逻辑/IO 分离 | 覆盖率阈值 + 可移植 SQL 扫描 | 强制 | 部分已有 |
| 4 错误是值 | lint 禁空 catch + 失败路径用例 | 半自动 | 待补 |
| 5 可观测 | 关键状态出口评审 + L4 冒烟据出口断言 | 半自动 | 随 L4 |
这个仓库的落点:.gitea/workflows/ci.yml(shellcheck、OpenAPI 校验、红线词扫描、flutter analyze + test)= 闸 4 骨架已在。缺:契约快照、golden 进 CI、可移植 SQL 扫描、pre-commit hook、自定义 lint、PR checklist。
换到你别的多端项目时,五条原则不变,只替换右列实例:
| 通用原则 | Pangolin 实例(换项目时替换) |
|---|---|
| 接缝即接口 | VpnBridge / KernelProcess / TokenStore / AuthApi(http.Client) |
| 契约单源 | stats EventChannel · /v1/* JSON · UsageEntry proto · colors_and_type.css |
| 纯逻辑/IO 分离 | 速率/时长格式化 · 配额计算 · 时间 Go 端算 · dialect 方言层 |
| 错误是值 | AuthApiException · 控制面错误码 |
| 可观测 | status/stats stream · 结构化日志 · /v1/usage · /healthz |
本规范为「怎么写」的真相源;「怎么验证」见 测试框架架构说明。两份配套维护,新增接缝时同步更新两边。