Files
erqiwang_youle/server/games/erqiwang/test/README.md
T
joywayerandClaude Opus 5 1b76683704 二七王:固化两组 design 差分测试(§7 全量对拍 + §5.2 穷举差分)
把第五轮复验用的取证探针固化入库。两者都不枚举「我想到的场景」,而是把
design 的规则整条转写成参考实现再与代码大面积对拍,专治手写用例覆盖不到
的欠约束分支:

- test_calc.js:§7 算子全量对拍。参考实现逐字转写自 §7.1/§7.2/§7.3 判定表,
  比对 2 种算子模式 × 14 个叫分档 × grade 0~260 共 7308 格(每格比
  base/Q/判定倍率/最终子数),另按 design 的 17 张表逐格抽查 141 个写死的
  最终子数。断言按档位分组,失败可直接定位。
- test_followdiff.js:§5.2 跟牌强制层级穷举差分。对每手牌枚举全部 C 张出牌
  组合,逐个比对参考裁定与 can_followcard,约 11 万组。

两组均通过变异检验:关掉 follow_tractor_cover_ok → followdiff 三种拖拉机
首出全红;大光倍率 3→4 / 常规55档 base 4→5 / 爬坡40·35档 Q 20→40 → calc
精确指出档位与 grade。

过程中的教训已写进 test/README 与 compliance:差分测试的强度取决于造牌器。
第一版纯随机抽牌跑 7.8 万组全绿,但关掉覆盖度校验依然全绿——「同花色三组
互不相邻的两连对」这种唯一能区分「只出零散对子」与「先凑最长拖拉机」的
12 张结构随机抽不出来;掺牌又一度把它撑过枚举上限被静默跳过。故随机源改用
固定种子 PRNG,关键结构用确定性 structHands() 钉死。

全套单测 401 → 462 项全绿,总耗时 < 1.5s。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 12:56:30 +08:00

59 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 二七王服务端单元测试
## 运行
```bash
node server/games/erqiwang/test/run.js # 跑全部单测
node server/games/erqiwang/test/test_arith.js # 单独跑某一组
```
退出码 0 表示全过,非 0 表示有失败;`run.js` 逐个以独立进程 spawn 各 `test_*.js` 并汇总。
## 原理:一套代码,两个运行时(dev-guide 01 §1)
正式代码(`class.*.js`)遵循平台的双运行时规范:
- **友乐 / 浏览器**:无 `require`,由 `mod.js` 用 `min_loadJsFile` 加载为**全局对象**;文件顶部的 `if (typeof require !== 'undefined')` 守卫被跳过,跨模块按**同名全局**引用。
- **Node(本地 / 单元测试)**:有 `require`,守卫块 `var X = require('./class.X.js')` 取到依赖;文件底部 `if (typeof module !== 'undefined') module.exports = ...` 导出。
因此这两处守卫是**纯增量**:友乐运行时行为完全不变,只在 Node 下额外启用 require/exports。
## `_shim.js`:平台全局垫片(仅测试用,不进正式代码)
正式代码按全局名引用少量平台工具(`min_ary_include/min_ary_deduct/min_random/min_now/min_ontimeout`)与模块入口 `youle_erqiwang.import`。友乐由平台提供,Node 单测由 `test/_shim.js` 提供等价实现(数组/随机工具忠实复刻 `server/minhttp.js`,`youle_erqiwang.import` 用 mock)。测试文件顶部先 `require('./_shim')` 再 `require('../class.*.js')`。
## 测试计划
完整的"规则 → 用例"覆盖矩阵、缺口清单与优先级见 [`../docs/compliance/02-测试计划.md`](../docs/compliance/02-测试计划.md)。
## 覆盖
- `test_arith.js`:主牌排序/相邻链(§3)、扣底倍数(§6.3)、算子常规+爬坡逐档+分界+升3级(§7)、算奖 get_chongguan(§8.1)、甩牌分解/最大性/合法性(§5.4)、跟甩牌强制分量拆解(§5.4.4)。
- `test_follow.js`:正常跟牌/毙牌/垫牌,单张/对子/拖拉机的必出与缺门毙牌数量对应(§5.1/5.2)。
- `test_callgrade.js`:叫分坐庄 do_callgrade(叫5立即/两家不叫/后叫更低)(§4.2)。
- `test_config.js`:roomtype 位串解析、局数/扣卡映射(§10.1)。
- `test_paiju.js`:亮牌 get_liangpai(§8.2,含「埋牌后静态快照、出牌后重连不缩水」回归)、结算 get_paiju_account 集成(§7/§8,大光/过庄/升级/爬坡/傍王/投降)、扣底触发 get_bottom_account(§6.3)。
- `test_deal.js`:发牌构成——92张/去3-4/三家28+底8/分值(§2/§6.1)。
- `test_desk.js`:坐庄轮换 do_prepare(庄赢连庄/闲赢·投降下家)(§4.6)。
- `test_endgame.js`(L4 端到端,用真实发牌跑完整一局):叫分→上庄→选主→埋牌→28 轮出牌→小局结算的流程闭合(§12.1),以及与牌面无关的不变量(牌张守恒 84/8/16、捡分=闲家赢得分牌+扣底分、`X` 分配庄±2X/闲∓X、算奖 `X×(2Ni−Nj−Nk)`、结算零和、`result` 与判定一致);另覆盖 §4.4/§7.1 投降局与 §12.2 中途解散两条支线。
- `test_rpc.js`(L3,用 `_rpc.js` 装配 mod.js + 捕获下发包):明牌 mingpai、出牌 chupai 的 seatlist/baozhu 查牌门控(§9,含「seatlist 整表带三家」)、投降 touxiang 条件、jiaofen 入参校验、70分上庄暗牌亮牌下发(§4)。
### 两组差分测试(按 design 原文另写参考实现,与代码对拍)
下面两组与上面的手写用例互补:它们不枚举"我想到的场景",而是**把 design 的规则整条转写成参考实现**,再和代码大面积比对,专治手写用例覆盖不到的欠约束分支。
- `test_calc.js`:**§7 算子全量对拍**。参考实现逐字转写自 §7.1/§7.2/§7.3 的判定表,与 `get_base_bycall` / `get_qvalue` / `get_upgrade` 比对 **2 种算子模式 × 14 个叫分档 × grade 0~260 = 7308 格**(每格比 base/Q/判定倍率/最终子数四项),另按 design 的 17 张表逐格抽查 141 个"写死的最终子数"。断言按(模式,叫分档)与表名分组,失败能直接定位到档位。
- `test_followdiff.js`:**§5.2 跟牌强制层级穷举差分**。参考裁定按 §5.2 原文写(缺门/不足/够牌 × 单张/对子/N 连对 + 最长拖拉机优先的档案比较),对每手牌**枚举全部 C 张出牌组合**逐个比对"参考允许 vs `can_followcard` 允许",任何一侧多放行或多拒绝都会被抓到(约 11 万组)。
这两组都做过**变异检验**(在副本上注入缺陷确认会转红):关掉 `follow_tractor_cover_ok` → `test_followdiff` 三种拖拉机首出全红;把大光倍率改 3→4、常规 55 档 base 改 4→5、爬坡 40/35 档 Q 改 20→40 → `test_calc` 精确指出档位与 grade。
两点写法约定,改这两个文件时别破坏:
- **随机源必须是固定种子的 PRNG**(`test_followdiff.js` 里的 xorshift32),不用 `Math.random`——同一份代码每次跑的用例集完全相同,不抖动。
- **关键结构要用确定性手牌钉死**,不能指望随机造牌覆盖到。例如「同花色三组互不相邻的两连对」(12 张)是唯一能区分"只出零散对子"与"先凑最长拖拉机"的形状(少于 3 组时任取 3 对必含相邻对,测不出差别);纯随机造牌造不出它,关掉覆盖度校验也全绿,差分就形同虚设。`structHands()` 就是为此存在的,另注意别让掺入的其他花色把手牌撑过枚举上限而被静默跳过。
## 约定(dev-guide §10 测试纪律)
- 测试代码可用现代 JS(不受严格 ES5 限制);**正式代码不得为测试放宽/加后门**。
- 正反面 + 边界都要覆盖;失败先裁根因(业务缺陷改业务、脚本缺陷改脚本),不软化断言。