diff --git a/docs/dev-conventions.html b/docs/dev-conventions.html new file mode 100644 index 0000000..1464f21 --- /dev/null +++ b/docs/dev-conventions.html @@ -0,0 +1,293 @@ + + + + + +Pangolin 开发规范 · 可测试性五支柱 + + + +
+ +

Pangolin 开发规范 · 可测试性五支柱

+

「怎么写才好测」——开发规范是可测试性的前置条件 · 与测试框架咬合

+ +
+核心立场:测试做不做得起来,根子在代码有没有遵守可测试的结构约定。哪里没遵守,哪里就测不了。这份规范把决定「能不能自动化测」的约定收成 5 条支柱,每条回答三件事:怎么写 → 所以测得了 → 由哪层守门。它与 测试框架架构说明 一一咬合:规范定义接缝,测试在接缝下刀。 + +
+ +

五支柱速览

+

写法采用通用原则 + Pangolin 实例双层——原则可复制到任何多端项目,实例替换即可。

+ +
+支柱 1 · 接缝即接口 +
所有跨进程 / 外部依赖(原生隧道、HTTP、子进程、存储、时钟、随机)藏在接口 / 可注入构造之后;业务逻辑里禁止直接 new 或调静态单例。分层单向依赖(token→实现→组件/页面),生成物 *.gen.dart 勿手改。
+
测试红利L2 集成/交互能换假替身,无需真依赖即可驱动真实路径。
+
验证钩子每个外部依赖都存在 Fake/Mock + 一条 flow 测试;依赖方向 lint(dart import / go internal 包边界)。
+
正 / 反例 VpnBridge 接口 → 可换 VpnBridgeMockAuthApi(baseUrl, http.Client) 可注入。 NETunnelProviderManager 直接散在 UI / 业务里 new。
+
+ +
+支柱 2 · 契约单源 +
每段跨边界数据(pangolin/vpn/stats EventChannel、HTTP JSON、gRPC UsageEntry proto、设计 token)有唯一定义源,两端从它生成或校验,禁止两端各写各的。
+
测试红利L0 契约快照能守住「字段形状」,接缝两端永不漂移。
+
验证钩子契约快照测试(字段 diff=0)+ codegen 漂移检查(colors_and_type.css*.gen.dart、proto→桩,生成物与源一致)。
+
针对这条正是「mac 实时统计恒为 0」的根因解药——两端各写 stats、无单源、无守门。
+
+ +
+支柱 3 · 纯逻辑与 IO 分离 +
计算 / 换算 / 状态机是纯函数,不混时间、网络、IO、随机;这些都当「可注入依赖」从外面传,IO 收进薄壳(functional core / imperative shell)。纯核心天然确定 → 不 flaky。
+
测试红利L1 单元无依赖、可重复、毫秒级;确定性是 E2E 不 flaky 的前提。
+
验证钩子覆盖率阈值;纯函数测试零 mock;时钟/随机可注入。
+
正例bytes→速率/时长 格式化、配额计算、时间 Go 端算好传 ?(不用 MySQL NOW())、golden 字体锁定
+
+ +
+支柱 4 · 错误是有类型的值 +
错误带类型 / 码、可断言;禁止吞异常、禁止只打日志不返回。失败路径和成功路径一样要可观测、可测。
+
测试红利测试能断言失败分支(401、超额、连接失败),而不是只测 happy path。
+
验证钩子错误路径用例;lint 禁空 catch {} / 裸吞;错误类型可枚举。
+
正例AuthApiException(statusCode, messageZh, messageEn)——带码、带双语、可断言。
+
+ +
+支柱 5 · 可观测即可断言 +
关键状态 / 事件经稳定通道暴露(status / stats stream、结构化日志、/healthz/v1/usage),让测试读数值断言,而非截屏猜
+
测试红利L4 端到端能断言:bytes 增长、限流触发、延迟 >0、状态流转。
+
验证钩子每个关键状态有可程序读取的出口;E2E 冒烟据此断言。
+
正例VpnStatusStreamHandler / stats EventChannel / /v1/usage 数值出口。
+
+ +

支柱 ↔ 测试层 咬合矩阵

+

每条支柱直接解锁测试架构里的某些层。这张图就是规范与测试的咬合证明:

+
+

图 1 · 五支柱解锁五测试层(● 主要解锁 ○ 间接帮助)

+ + + + L0契约 + L1单元 + L2集成·交互 + L3视觉 + L4E2E + + + + + + + + + + + + + + + + + + + 支柱 1 · 接缝即接口 + 支柱 2 · 契约单源 + 支柱 3 · 纯逻辑/IO 分离 + 支柱 4 · 错误是值 + 支柱 5 · 可观测 + + + + + + + + + + + + + + + + + + + + + + + + + 主要解锁 + 间接帮助 + + +
+

读法:补齐支柱 2(契约单源)就点亮 L0;做好支柱 1 才有 L2 的假桥;支柱 5L4 端到端能断言的唯一前提。规范不是道德要求,是测试能力的开关。

+ +

反例 → 真实 bug → 本可由哪条支柱防住

+

规范的说服力来自真实的坑。下表把项目里遇到/潜在的问题,回溯到被违反的支柱:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
现象 / 真实 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」硬闸,外加评审兜底

+ + ← 越早拦,成本越低 + + 写之前 + 写的时候 + 提交时 + push · PR + + + + + + + + + 闸 1 · 脚手架 + 接口 + Fake 骨架 + codegen 默认 + 预防 + + 闸 2 · lint/analyze + 空 catch · import 方向 + IDE 即时报 + 即时 + + 闸 3 · pre-commit + format · 快检 + codegen 漂移检查 + 本地拦 + + 闸 4 · CI 硬闸 + 全量测试 · 契约快照 + 覆盖率 · golden · E2E + 挡 merge + + + + + + + + + + 评审清单(人治兜底) · 机器判不了的:接缝抽象是否合理 / 关键状态有没有留可观测出口 + +
+ +

诚实区分「能不能机器强制」——能强制的硬卡 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。

+ +

可复用:通用原则 vs 本项目实例

+

换到你别的多端项目时,五条原则不变,只替换右列实例:

+ + + + + + + + + +
通用原则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
+ +

本规范为「怎么写」的真相源;「怎么验证」见 测试框架架构说明。两份配套维护,新增接缝时同步更新两边。

+ +
+ + diff --git a/docs/index.html b/docs/index.html index 392f71b..1128303 100644 --- a/docs/index.html +++ b/docs/index.html @@ -76,8 +76,23 @@
Windows 客户端的分步实现计划。
docs/superpowers/plans/2026-06-21-windows-client.md
+ +
统计体系整改 实现计划(#5)HTML
+
阅读版;执行真相源为 docs/superpowers/plans/2026-06-24-stats-overhaul.md(含 checkbox)。三期:四端实时统计修复 → 每设备归因+GB综合配额 → 统计页重设计(上聚合·下分设备)。
+
docs/stats-overhaul-plan.html
+

知识库 / 调研

+ +
开发规范 · 可测试性五支柱 HTML
+
「怎么写才好测」——开发规范作为可测试性前置条件。五支柱(接缝即接口/契约单源/纯逻辑分离/错误是值/可观测)+ 支柱↔测试层咬合矩阵图 + 反例→真实bug→对应支柱对照表。与测试框架文档咬合。
+
docs/dev-conventions.html
+
+ +
统一测试框架 · 架构说明 HTML
+
多端项目可复用的分层测试方法论(L0 契约→L4 E2E)+ 四类诉求(界面一致/交互/数值/后端记账)→层→工具→量化指标映射 + 五张架构图(金字塔/契约接缝/CI 流水线/E2E 拓扑)。
+
docs/test-architecture.html
+
KillSwitch 设计与跨平台能力矩阵 HTML
断网保护 L0–L3 分级模型 + 各平台能力天花板 / 当前实现矩阵。KillSwitch 决策依据。
diff --git a/docs/test-architecture.html b/docs/test-architecture.html new file mode 100644 index 0000000..a4b4bfc --- /dev/null +++ b/docs/test-architecture.html @@ -0,0 +1,431 @@ + + + + + +Pangolin 统一测试框架 · 架构说明 + + + +
+ +

Pangolin 统一测试框架 · 架构说明

+

分层方法论 + 脚手架模板 + 单一编排入口 · 可自动化 / 可量化 / 可自定义

+ +
+定性(方案 A):不存在一个能同时覆盖「界面渲染 / 交互驱动 / 后端记账 / 真链路端到端」的单一工具——这四者运行时根本不同。所以「一套测试框架」= 一套分层方法论 + 一组可复用脚手架模板(假桥、Provider 覆盖、golden+字体锁、Go 真库套、E2E compose 骨架)+ 一个统一编排入口。跨多端项目的复用方式是模板复制后按接缝适配,而非全项目 import 同一个包。 +
↔ 配套文档:开发规范 · 可测试性五支柱(「怎么写才好测」——测试能力的前置条件)
+
+ +

设计原则

+ +

前不久的「mac 实时统计恒为 0」就是典型反例:原生侧产出统计、Dart 侧消费统计,两端的 pangolin/vpn/stats 契约没有任何自动化测试守门,触发条件一变(隧道已连接才启动 app)就静默失效。L0 契约层防的正是这类 bug。

+ +

① 分层测试金字塔

+
+

图 1 · 五层金字塔:L0 契约为地基,L1→L4 自底向上

+ + + + + + + + + + + L4 E2E + L3 视觉一致性 + golden · pixelmatch + L2 集成 · 交互 + flutter flow · httptest · bufconn · 真库 + L1 单元 + flutter_test · go test+testify(纯逻辑) + L0 契约 / 静态闸 · 地基 + 契约快照 · OpenAPI 校验 · analyze/vet · 红线词扫描 + + + + L4 E2E + docker-compose 真链路冒烟 + + + + + + 越往上:越真实·越慢·越脆·越少 + 越往下:越多·越快·越稳·越省 + +
+ +

② 契约接缝数据流

+

统计/记账数据沿一条跨进程链路流动。每段接缝标注其契约,以及哪一层测试在此下刀——这张图就是「该测什么、在哪测」的地图。

+
+

图 2 · 契约接缝:从节点数据面一路回到客户端 UI

+ + + + + 原生隧道 / libbox + sing-box(客户端) + + + Flutter Dart 层 + UI / Riverpod + + + Go 控制面 + :8080 HTTP + DB + + + agent + :9443 gRPC mTLS + + + 节点数据面 + sing-box 出网 + + + + + + + + + + + + + ① stats EventChannel + ② /v1/* HTTP JSON + ③ UsageEntry gRPC + ④ V2Ray stats + + + + + + L0 契约 + L2 flow + VpnBridgeMock + + + L2 httptest + ApiClient 注入 + + + L2 bufconn ✓强 + 自签 CA/CRL + + + L4 真流量 ✗缺 + E2E 待建 + + + + + + + + + + +
+

实时上下行走 ①(libbox→EventChannel→Dart);累计流量/周柱走 ④→③→DB→②(节点统计→agent→控制面入库→客户端拉取)。两条流共用同一张接缝图,测试各自归位。

+ +

③ 四类诉求 → 层 → 工具 → 量化指标

+

把你提出的四类测试诉求逐条落到层、工具、量化指标与现状:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
诉求工具量化指标现状
① 界面设计一致
每端一致
L3Flutter golden(字体锁定,host 无关→天然每端一致);design-distill pixelmatch 对照 HTML 原型真相源像素不一致比例 vs 阈值 0.05;golden 通过/失败素材齐 · CI 没跑
② 前端交互
连接 / 登陆 / 登出
L2flutter_test 进程内驱动整树 + VpnBridgeMock + http MockClient + ProviderScope 覆盖流程通过/失败;覆盖率 lcov素材齐 · 待组装
③ 数值型
统计 / 实时速度 / 时延
L1 + L4L1:格式化/换算纯逻辑单测;L4:连真节点拉 stats 断言 bytes 增长、延迟 >0实测 vs 期望容差;时延 ms;速率 B/s >0L1 可补 L4 缺
④ 后端网络
按租户流量 / 限流
L2 + L4L2:SQLite 真库 + testcontainers MySQL(已覆盖并发/重放/跨日/配额);L4:全链路真流量计数记账数值断言;限流是否触发;覆盖率 go coverL2 强 L4 缺
+ +

④ 逐层详解

+ +
+L0 · 契约 / 静态闸 +

守住接缝的「字段形状」

+ +
+ +
+L1 · 单元 +

纯逻辑,最快最稳

+ +
+ +
+L2 · 集成 / 交互 +

带假替身的真实路径

+ +
+ +
+L3 · 视觉一致性 +

每端渲染一致 + 对照设计真相源

+ +
+ +
+L4 · 端到端冒烟 +

真链路:统计真不真 / 流量真不真

+ +
+ +

⑤ 编排与 CI 流水线

+

一个入口按 profile 分流:CI 每 push 只跑快而稳的 fast;视觉与 E2E 按需触发。

+
+

图 4 · 单一入口 → 三 profile 泳道

+ + + + 单一入口 + make test / test.sh + + + + + + + + + + + fast + L0 + L1 + L2(假桥/真库) + + 触发:每次 push(nas CI) + 产物:通过/失败 + lcov 覆盖率 + + + + visual + L3 golden + pixelmatch + + 触发:push 或本地 + 产物:pixel diff 报告(HTML) + + + + e2e + L4 真链路冒烟 + + 触发:本地 / 按需(docker±Mac) + 产物:数值容差报告 + + + + + + + + +
+ +

⑥ L4 端到端冒烟拓扑

+
+

图 5 · docker-compose 真链路 + 测试驱动器(推荐边界 a)

+ + + + 测试驱动器 + + ① 登陆 /v1/auth/login + ② 发起连接(真凭证) + ③ 发真流量(curl/iperf) + ④ 拉 /v1/usage + ⑤ 断言 bytes↑ / 限流 + + + + + docker-compose · 一台 Linux runner + + + + 控制面 :8080 + HTTP + gRPC :9443 + + + SQLite + Redis + 记账存储 + + + agent + mTLS enroll · 上报 + + + sing-box + 数据面出网 + + + 回环出口 + httpbin/echo + + + + + + + + + + + + + + + ① ② ④ + + + + + ③ 真流量经数据面出网 → 计数 + +
+ +

⑦ 量化指标总表

+ + + + + + + + + +
维度指标工具 / 口径
覆盖率行/分支 %flutter test --coverage + go test -coverprofile → 合并 lcov
视觉不一致像素比例 vs 阈值 0.05pixelmatch(diff.mjs);golden 通过/失败
数值精度实测 vs 期望容差L4 断言 bytes/速率/累计落在容差带内
时延毫秒 msurltest 延迟 >0 且在合理区间
稳定性通过率 / flaky 率多次重跑统计;flaky 用例隔离
+ +

⑧ 可复用与可自定义

+

跨项目模板(复制即用,按接缝微调)

+ +

每项目按接缝定制:契约字段、API 路径、compose 服务清单、golden 屏清单、数值容差阈值。

+ +

⑨ 落地路线(建议)

+ + + + + + + + + +
动作性价比起点
1L0 契约快照 + 把现有 golden 接进 CI最高(防回归)素材已有,组装即可
2组装 L2 前端 flow 测试(登陆/续登/连接/登出)Mock/覆盖素材全齐
3L4 后端全链路冒烟 (a)中(覆盖"真不真")需新建 compose + 驱动器
4补 L1 数值单测 + L2 HTTP 鉴权测试增量补齐
5(b) desktop 真连 / (c) 真机——本地按需低(贵·脆)需常驻 Mac / 真机
+

本文档仅为架构说明(设计真相源)。具体落地拆解(契约快照实现、前端 flow 用例、L4 冒烟脚本)走各自实现计划(writing-plans)。

+ +
+ +