报告:补 P2 批次(覆盖率口径与关键页冒烟)记录

This commit is contained in:
tamakyi
2026-09-24 22:48:32 +08:00
parent 256620ee3d
commit f2ce6646c4
+104
View File
@@ -1076,3 +1076,107 @@ go test -count=1 -v ./...
---
# 修复记录(第十二批 · P2:覆盖率口径与关键页冒烟)
## 一、先纠正一个测量错误:`rond` 不是 38.8%
看到「`rond` 只有 38.8%」本来准备补测试,取证后发现**数字是量错了**:
`go test -cover` 只统计**测试文件所在那个包**自己的执行。而 `internal/rond` 一半的代码
(`export.go` 1416 行)由 `internal/web` 的 `TestMovementCleanExport` 驱动;`geo`/`gpx`/
`notes`/`votes` 同理由 `web`/`stats` 驱动。按 `-cover` 量,这些包一律报 0%。
| 包 | `-cover`(假口径) | `-coverpkg=./internal/...`(真) |
|---|---|---|
| `internal/rond` | 38.8% | **82.2%** |
| `internal/geo` | 0% | **92.9%** |
| `internal/stats` | 62.2% | **83.7%** |
| `internal/web` | 53.2% | 53.2%(它自己不假) |
⇒ `verify.sh --cover` 改用 `-coverpkg=./internal/...`,并按包打印。**项目合计 62.3%**。
解析 profile 时有个坑:`-coverpkg` 下每个测试二进制都会给同一段代码写一条记录,
同一个 `(文件,行段)` 会出现多条(一条 `count=0`、一条 `count>0`)。**必须按块取 max**;
直接求和等于把语句数乘上二进制份数,每包都会显示成只有 `1/N`(第一版 awk 就这么错的,
`web` 印成 4.4%)。
## 二、真去补的时候,只补「线上会走到」的
把未覆盖行段拉出来逐条看,绝大部分是 `nil` 分支和 `if err != nil { return err }`——补那些是灌水。
真正「线上会走到、却一条用例都没有」的只有两处:
**① `BuildMergePlan` 的地点/活动冲突**(`placePick`/`activityPick` 长期 0%)。差异页线上确实会提交
这两类选择,而用例只造过「新增」。补 `merge_clash_test.go`(冲突 + 删除逐条勾选)后
`merge.go` 64.3% → **96.4%**。
变异验证:把地点判据从 `PickBackup` 改成 `PickDB` ⇒ 红在「只有选了『用实机』的地点该更新」;
把活动键从 `activityKey(cf.DB)` 改成 `cf.DB.Name` ⇒ 红在「有 UUID 的活动应按 UUID 对上」。
**② `internal/gpx` 整包零测试**(61 条语句,0/61)。它解析的是**用户上传的 XML**,
正是最该有测试的地方。补 `gpx_test.go` 8 条:`trk`/`rte` 两条分支、track 名退回 `metadata>name`、
`(0,0)` 空点丢弃、单点轨迹丢弃并报错、非法 XML、没有轨迹点、时间格式错不致命、里程数值。
⇒ **0% → 96.0%**。
变异验证:`(0,0)` 跳过改成 `if false` ⇒ 红在「该被丢掉,实际剩 3 个点」;
地球半径 6371→6300 ⇒ 红在「1 度纬度该约 111.19 km」。
## 三、顺手清掉两处死代码
两者都做过全仓核查(含模板——Go 模板能调方法,`{{.Total}}` 这种不能只搜 `.go`):
| 死代码 | 核查结果 |
|---|---|
| `rond.Counts.Total()` | 零引用。模板里的 `.Total` 都是各页 view 结构体自己的字段。 |
| `gpx.Track.BBox()` | 零引用。`handlers_topics.go` 里的 `fitBBox` 是无关变量,`gpx.Track` 从未传进模板。 |
**没有为它们写测试**——为了把 0% 填成 100% 而给死代码补用例,只会把死代码固化下来。
## 四、一条「实测否证」的记录
`geo.Haversine` 用 `math.Asin(math.Min(1, math.Sqrt(a)))` 夹了一道,`gpx` 那份没有,
SQL 里(`handlers_topics.go`)也没有。三条公式、两个半径(6371.0 / 6371.0088)。
本来准备按「缺夹子会出 NaN」去修,**实测不成立**:对跖点附近 `h` 最多到 `1+2^-52`
((45,0)→(-45,180) 就是这个值),而 `math.Sqrt(1+2^-52)` 会回舍成正好 `1.0`,
`asin(1.0)=π/2`,出不了 NaN。所以**没改代码**,只留一条 `TestLengthKmAntipodalIsFinite`
把「对跖点不出 NaN」钉住(将来换公式或去掉开方会立刻红),并在注释里写明这是实测结论。
半径差 6371.0 vs 6371.0088 的数值影响:146 km 上差 **0.20 米**,纯属口径不统一,不影响功能。
## 五、关键页冒烟:`scripts/smoke.py`
Go 侧模板守卫只能证明「模板里有那段文字」。白屏、JS 抛错、内容区块被压成 0 高,
**模板测试全是绿的**。冒烟按三种口径各走一遍(16 页):
1. **站长(浏览器)**:HTTP 200 + 落点路径没跳走 + 内容区块在且尺寸非 0 + 零 JS 报错
2. **访客(纯 HTTP)**:公开页 200;后台页必须 **303 跳登录**(绝不能 200)——`urllib` 不跟随跳转即可,
比开 Chrome 快一个数量级
3. **访客(浏览器)抽检** `/` 与地点详情:只在访客侧复现的 bug 藏在这里
接进 `verify.sh --smoke`(只跑冒烟)/ `--all`(全量 + 冒烟)。
三条实测踩出来的坑:
- **关键区块不能取 `nav.nav`**:它在 `layout.html` 里,内容块整个没渲染它照样在,等于没断言。
改成地图页取地图容器、其余取首个 `.card`(`#home-map` / `#map` / `#place-map` / `#taxonomy`)。
- **用 `.card` 而不是 `div.card`**:专题列表的卡片是 `<a class="card topic-card">`,
写 `div.card` 一个都选不中——而 `querySelector` 返回 null 时尺寸也是 0,打印出来跟
「元素被压成 0 宽」一模一样。第一版就据此差点去查布局 bug。日志已把两者分开印。
- **`--min-body` 只是兜「整页空白」**:专题列表正文天生 166 字(就一个专题卡片),阈值定 200 会假红。
另外「公开页面」是受总开关控制的,冒烟先探测 `/places` 的访客状态码再决定同类页的期望值。
**变异验证**:往 `timeline.html` 塞一句 `<script>throw new Error('smoke-mutation-probe')</script>`
(`debug=true` 下模板热加载,不用重启)⇒ 冒烟立刻红在「JS 报错」上,且**状态码、内容区尺寸全都正常**
——正好说明没有这条断言就会放过它。验完已还原(`git status` 无该文件)。
## 六、收尾验收
```
./scripts/verify.sh # 284 个顶层用例(含子测试更多)/ 0 FAIL / 0 SKIP
./scripts/verify.sh --all # 全量 + 冒烟 16 页全绿
./scripts/verify.sh --cover # 按包覆盖率 + 项目合计 62.3%
./scripts/verify.sh --fast # gofmt / vet / build 干净
```
`--fast` / `--smoke` / `--cover` / `--all` / 未知参数 → exit 2 的门都实测过。