From 29ddd5b4532415e97c3de60b068eef829b3288e7 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 24 Jun 2026 23:25:14 +0800 Subject: [PATCH] =?UTF-8?q?docs(test):=20=E6=B5=8B=E8=AF=95=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E6=9E=B6=E6=9E=84=20+=20=E5=BC=80=E5=8F=91=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E4=BA=94=E6=94=AF=E6=9F=B1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/test-architecture.html:分层测试金字塔(L0契约→L4 E2E) + 契约接缝 数据流 + 四类诉求映射 + CI 流水线 + E2E 拓扑,5 张内联 SVG/表格图 - docs/dev-conventions.html:可测试性五支柱(接缝即接口/契约单源/纯逻辑分离/ 错误是值/可观测) + 支柱↔测试层咬合矩阵 + 反例对照表 + 执行机制四道闸 - docs/index.html:登记两份文档到知识库/调研,双向互链 Co-Authored-By: Claude Opus 4.8 --- docs/dev-conventions.html | 293 ++++++++++++++++++++++++ docs/index.html | 15 ++ docs/test-architecture.html | 431 ++++++++++++++++++++++++++++++++++++ 3 files changed, 739 insertions(+) create mode 100644 docs/dev-conventions.html create mode 100644 docs/test-architecture.html 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 统一测试框架 · 架构说明

+

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

+ +
+ +

设计原则

+ +

前不久的「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 · 契约 / 静态闸 +

守住接缝的「字段形状」

+
    +
  • 测什么:跨进程契约的结构——pangolin/vpn/stats EventChannel 字段、gRPC UsageEntry proto、/v1/me·/v1/usage JSON 形态;静态:analyze/vet、OpenAPI 结构、红线词。
  • +
  • 工具:契约快照测试(snapshot)+ 已有 OpenAPI 校验 + ci/scan-redline.sh
  • +
  • 量化:字段 diff = 0;扫描命中 = 0。
  • +
  • 现状/缺口OpenAPI/红线已有 契约快照需补这是最被低估、性价比最高的一层。
  • +
  • 可自定义:新增一段接缝 = 加一份快照基线。
  • +
+
+ +
+L1 · 单元 +

纯逻辑,最快最稳

+
    +
  • 测什么:数值换算(bytes→速率/时长格式化)、状态机(ConnectionController)、记账纯函数(GB 卡控、dp_uuid 解析)。
  • +
  • 工具flutter_testgo test + testify。
  • +
  • 量化:覆盖率(lcov / go test -cover)。
  • +
  • 现状已有(前端 controller、后端 usage 包多场景)。
  • +
+
+ +
+L2 · 集成 / 交互 +

带假替身的真实路径

+
    +
  • 前端交互pumpWidget(真 App 树) 驱动 登陆→重启续登→点连接→状态流转→登出,把原生隧道换 VpnBridgeMock、服务端换 MockClientProviderScope 覆盖注入。
  • +
  • 后端 DB:SQLite 内存真库 + testcontainers MySQL/Redis(已覆盖并发去重/重放/跨日分桶/配额)。
  • +
  • 后端 HTTP/gRPChttptest.Server(鉴权路径需补);gRPC bufconn + 自签 CA/CRL(已强)。
  • +
  • 量化:通过/失败 + 覆盖率 + 数值断言。
  • +
  • 现状后端强 前端 flow 待组装 · HTTP 鉴权偏薄
  • +
+
+ +
+L3 · 视觉一致性 +

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

+
    +
  • 测什么:mobile/tablet/desktop × light/dark × zh/en 的渲染一致;以及实现 vs HTML 原型的像素差。
  • +
  • 工具:Flutter golden(flutter_test_config.dart 字体锁定→host 无关,所以「每端效果一致」天然成立);design-distill shoot-prototype.mjs + diff.mjs(pixelmatch)。
  • +
  • 量化:像素不一致比例 vs 阈值 0.05
  • +
  • 现状20+ golden 基准已有 CI 未接 · pixel 闸未自动化
  • +
  • 可自定义:新增屏 = 加一张基线;阈值按屏可调。
  • +
+
+ +
+L4 · 端到端冒烟 +

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

+
    +
  • 测什么:起真控制面 + agent + sing-box + DB,发真流量,断言按租户/按设备 bytes 入库、限流触发、实时数值 >0。
  • +
  • 工具:docker-compose + 探针驱动脚本(见图 5)。
  • +
  • 量化:实测 vs 期望容差、限流是否触发、连上耗时、时延 ms。
  • +
  • 现状完全缺——最大缺口local_test.sh 只构建启动、deploy.sh 只部署,均无连通性自检。
  • +
  • 边界(建议):先做 (a) 后端全链路(可全自动、进一台 Linux runner);(b) desktop 真连(需常驻 Mac)与 (c) iOS/Android 真机 作本地按需后续。
  • +
+
+ +

⑤ 编排与 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)。

+ +
+ +