二七王:制定服务端合规测试计划

- docs/compliance/02-测试计划.md:按 design 章节的"规则→用例"覆盖矩阵
  (标注被测函数/测试层L1-L3/正反边界/覆盖状态),缺口清单按优先级排序,
  以及 L2 局内集成 / L3 RPC 层所需脚手架
- test/README.md:链接测试计划

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 00:02:52 +08:00
co-authored by Claude Opus 4.8
parent 6d663c8a66
commit 5a5165a319
2 changed files with 151 additions and 0 deletions
@@ -0,0 +1,147 @@
# 二七王服务端 · 合规测试计划
> 目标:用 Node 单测/集成测**系统覆盖 `design.md` 的全部服务端可验证规则**,做到"规则 → 用例"可追溯,作为"代码是否符合手册"的**自动化证据**(与 `01-design合规逐节核对.md` 的人工审计互补)。
>
> 运行:`node server/games/erqiwang/test/run.js`。测试原理与双运行时见 `test/README.md`。测试纪律(正/反/边界、不软化断言、失败先裁根因)见 `docs/server/development-guide/04` §10。
---
## 0. 分层与测试类型
| 层 | 含义 | 依赖 | 现状 |
| --- | --- | --- | --- |
| **L1 单元** | 纯函数直接调用(arith/config/paiju 静态计算) | 仅 `_shim` 的平台工具 | 已有 `test_arith`/`test_config`/`test_paiju`(部分) |
| **L2 局内集成** | `cls_youle_erqiwang_paiju.new(o_desk,firstseat)` 造牌局,驱动 `do_callgrade→do_choiceflower→do_burycard→do_playcard…→get_paiju_account` 验证状态/结算 | mock `o_desk`/`o_room` + shim;**不经 mod.js** | 仅 `get_paiju_account` 造了极简对象,**无完整一局驱动** |
| **L3 RPC/广播** | `mod.js` 收包入口 → 校验 → 广播 | mock `check_player`/`SendPack`/`sendpack_toother`,捕获下发包 | **完全未覆盖** |
**发牌随机性处理**:`do_dealpai` 用 `min_random`。L2 需要**可控发牌**——测试 `_shim` 提供可注入的 `min_random`(预置序列或指定发牌结果),使一局可复现。这是 L2 的前置基建。
---
## 1. 覆盖矩阵(按 design 章节)
状态:✅ 已覆盖 · 🟡 部分 · ❌ 待补。用例列标注 正/反/边界 三类(`docs/server/development-guide/04` §10 要求三类齐全)。
### §2 牌局构成
| 规则 | 被测 | 用例(正/反/边界) | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 92 张、去两副 3/4 | `init_cards`/`do_dealpai` | 正:发完 3 家各 28 + 底 8 = 92;反:牌堆不含 number 3/4;边界:两副各花色计数 | L2 | ❌ |
| 分值 5→5、10→10、K→10、余 0 | `init_cards` | 正:各面值 score;边界:非分牌 score=0 | L1/L2 | 🟡(扣底/结算间接) |
### §3 主牌顺序 / 编码
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 主牌 code 降序=设计顺序(大王>小王>正7>副7>正2>副2>主A…主5>副牌) | `id_to_code`/`order_cards` | 正:排序结果逐位;边界:正/副 7、正/副 2 的相对大小;主A vs 副2 | L1 | ❌(间接用到,无专测) |
| 相邻链 + 6/8 可连、7 不与 6/8 连 | `is_continuous` | 正:全相邻段逐对;反:7-6/8-7/9-7 不连;边界:8-6 连、大王-小王连 | L1 | 🟡(甩牌/算奖间接) |
| 副7/副2 跨花色比大小归一 | `trump_rank`/`can_followcard` | 正:副7 vs 副7 不可压;反:正7 压副7 | L1 | 🟡(甩牌间接) |
### §4 开局与坐庄
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 叫分 5~70、步进5、暂定庄必叫、后叫更低/不叫 | `mod.jiaofen` 校验 + `do_callgrade` | 正:合法叫分推进;反:>70/非5倍/首家0/叫≥当前;边界:叫70、叫5 | L3(校验)+L1(do_callgrade) | ❌ |
| 叫5立即上庄、两家不叫上庄 | `do_callgrade` | 正:叫5→banker;正:一家叫另两家不叫→banker;边界:三家叫分序列 | L1 | ❌ |
| 坐庄轮换:庄赢连庄、庄输/投降下家、首局庄=0号 | `desk.do_prepare`/`makewar` | 正:result=0 连庄;正:result=1/2 顺延;边界:首局 firstseat=0 | L2/L3 | ❌ |
| 阶段机 1→2→3→5→6,无 step4 | `do_up_banker`/`do_choiceflower`/`do_burycard` | 正:各 handler 后 step 值;反:错误 step 调用被拒 | L2/L3 | ❌ |
| 投降仅70分/step2/庄家/不埋牌 | `mod.touxiang` | 正:70分 step2 投降→结算;反:≠70/step≠2/非庄;边界:投降后 step=6 | L3 | ❌ |
| 70分暗牌向所有玩家亮3秒(+ancard3s)、非70只发庄 | `mod.jiaofen`(shangzhuang) | 正:70→闲家有 bottomcards+ancard3s;反:非70 闲家无 bottomcards | L3 | ❌ |
| 选主后先选主后埋牌 | `do_choiceflower`/`do_burycard` | 正:flower 记录、step 推进;边界:埋牌须8张且在手 | L2 | 🟡(check_cards_inhand 未测) |
### §5 出牌规则
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| **§5.1/5.2 跟牌/毙牌/垫牌/对子/拖拉机必出** | `get_followcard`/`can_followcard` | 正:同花色跟、有对必出对、有拖必出拖、两对不连必出;反:缺牌型被拒;毙牌:副单→主单、副对→主对、副N连→主N连;垫牌:随意副牌 | L1 | ❌ **(重大缺口,仅甩牌 follow 有测)** |
| §5.3 拖拉机相邻 | `is_continuous`/`get_tuolaji_list` | 见 §3 | L1 | 🟡 |
| §5.4 甩牌:副禁甩/最大性/甩错 | `can_playcard`/`opp_can_beat_flush`/`decompose_trump` | 已覆盖(合法/甩错含smallest/副禁甩/单张/对子/拖拉机) | L1 | ✅ |
| §5.4.4 跟甩牌强制分量拆解 | `flush_follow_ok` | 已覆盖(有对必打对/有拖必打拖/退化/主牌最大化/无主全垫) | L1 | ✅ |
### §6 捡分与扣底
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| §6.1 分值 | `init_cards` | 见 §2 | L1 | 🟡 |
| §6.2 闲家赢归闲、庄赢作废、两闲谁赢都算闲 | `do_playcard`/`get_jian_grade` | 正:闲赢累计分;反:庄赢不计;边界:两闲各赢 | L2 | 🟡(1 集成例) |
| §6.3 扣底倍数 单1/对2/N连2N、全主牌守卫、取最高规格 | `get_bottom_multiple` | 已覆盖 | L1 | ✅ |
| §6.3 触发:闲家用主牌赢末轮才扣底 | `get_bottom_account` | 正:闲家主牌赢末轮翻倍;反:非主牌赢不扣、庄家赢不扣 | L2 | ❌ |
### §7 结算:子数与升级
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 常规算子 基础子数 + 大光×3/小光×2/过庄×1/升N级、Q=40 | `get_base_bycall`/`get_qvalue`/`get_upgrade` | 已覆盖各档代表值 | L1 | ✅(可补每档过庄/小光分界与升3级) |
| 爬坡 基础子数梯度 + 分段Q | 同上(climb=true) | 已覆盖各档 | L1 | ✅ |
| X=基础×倍率、庄赢/闲赢符号、按房间爬坡切换 | `get_paiju_account` | 正:大光/过庄/升级/爬坡局 seatlist;边界:banker=-1 | L2 | 🟡(仅65小光+投降) |
### §8 算奖
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| §8.1 常规算奖只庄家、三/四王+连对链、6-8个7/2、无≥10老主/无作废 | `get_chongguan` | 已覆盖 | L1 | ✅ |
| §8.2 亮牌阈值/统计、只亮结构 | `get_liangpai` | 已覆盖 4 例 | L1 | 🟡(补 王≥3/7≥6 组合、边界9/10) |
| §8.3 傍王按位开关、庄闲每王1奖 | `get_paiju_account`(bangwang) | 正:傍王局 grade_aw 含王奖;反:未勾选不计 | L2 | ❌ |
| §8.4 算奖并入 X×(2Ni−Nj−Nk)、含闲-闲、投降X=1 | `get_paiju_account` | 正:庄1奖/闲傍王2奖的三家净额;边界:三家同时有奖 | L2 | ❌(公式单测有,集成未测) |
| §8 快照:庄埋后28/闲发后28;投降庄36无主 | `get_seat_cards_award` | 正:庄排除已埋;正:投降含底36;边界:无主花色无连对链 | L2 | 🟡(投降 count 间接) |
### §9 查牌 / 明牌
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 报无主统计 info/baozhu 门控 | `mod.chupai` | 正:可查牌下发 info/baozhu;反:不查牌 info 缺失、baozhu=0 | L3 | ❌ |
| 亮牌/PushCards.seatlist 门控 | `mod.maipai`/`get_deskinfo` | 正:可查牌闲家有 liangpai;反:不查牌无 | L3 | ❌ |
| 明牌 mingpai:可查牌+已报无主+出牌阶段 | `mod.mingpai` | 正:条件满足返回他家主牌;反:不查牌/未报无主/非出牌阶段被拒 | L3 | ❌ |
### §10 房间设置
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 位串解析、局数6/12、扣卡房主2·4/AA1·2 | `config.parse`/`export.get_asetcount`/`get_needroomcard` | 已覆盖(config 复刻映射) | L1 | ✅(可改为直接调 export 函数) |
| 傍王/爬坡/查牌三开关生效 | `get_paiju_account`/`mod.*` | 傍王/爬坡见 §7/§8;查牌见 §9 | L2/L3 | 🟡 |
### §12 完整流程
| 规则 | 被测 | 用例 | 层 | 状态 |
| --- | --- | --- | --- | --- |
| 一局端到端:发牌→叫分坐庄→选主→埋牌→逐轮出牌→末轮扣底→结算→轮庄 | 全链路 | 正:一局可控发牌打到结算,各家总分正确;含连庄/下家轮转 | L2 | ❌ |
> §11 牌局交互提示为纯客户端展示,无服务端可测项。
---
## 2. 待补用例清单(按优先级)
### P0 · 规则/金额关键、且当前零覆盖
1. **§5.1/5.2 正常跟牌/毙牌/垫牌**(`get_followcard`/`can_followcard`)——最大缺口。单张/对子/拖拉机的必出与毙牌数量对应、垫牌、缺牌型拒绝,正反边界全套。
2. **§7/§8 结算集成扩充**(`get_paiju_account`,L2)——大光/过庄/各升级级数、爬坡局、傍王局、算奖参与(庄含奖 + 闲傍王)、投降 36 张快照、末局触发大局结算。
3. **§4.2 叫分坐庄**(`do_callgrade`,L1)——叫5上庄、两家不叫上庄、后叫更低约束、首家必叫的判定序列。
4. **§6.3 扣底触发**(`get_bottom_account`,L2)——闲家主牌赢末轮才扣底/翻倍;非主牌赢或庄赢不扣。
### P1 · 规则关键、当前间接覆盖或需 mock
5. **§9 查牌门控 + mingpai**(L3,需 mod RPC 脚手架)——info/baozhu/liangpai 按 `nocheck` 门控、明牌 RPC 条件。
6. **§4 投降 + 暗牌亮牌**(L3)——touxiang step2/70分互斥、shangzhuang 暗牌 70 分发全员+ancard3s、非70只发庄。
7. **§4.6 坐庄轮换**(`do_prepare`,L2/L3)——庄赢连庄/庄输下家/首局0号。
8. **§2/§3 构成与编码专测**(L1/L2)——92张/去3-4/分值;id_to_code 排序、is_continuous 全链。
### P2 · 补强既有
9. §8.2 亮牌 更多阈值组合与 9/10 边界。
10. §7 算子 每档过庄/小光分界与升3级补齐。
11. §10 改为直接调 `export.get_asetcount`/`get_needroomcard`(替代 config 复刻)。
---
## 3. 集成脚手架需求(实现 L2/L3 的前置)
- **L2 局内驱动器 `test/_harness.js`**:
- 可注入发牌的 `min_random`(或直接构造 `o_paiju.cards` 指定各家手牌),使牌局可复现。
- mock `o_desk`(seatlist 累积、`get_desk_account`、`o_room.roomtype/asetcount`)与 `o_room`。
- 提供"驱动一局到某阶段"的辅助:叫分序列 → 选主 → 埋牌 → 出牌序列。
- **L3 RPC 捕获器**:mock `youle_erqiwang.import.check_player`(返回构造的 `o_room`)、`o_room.method.sendpack_toother`/`youle_erqiwang.app.SendPack`(把下发包收集进数组),断言包字段(含 §9 门控、暗牌、投降、chupai1/2/3、jiesuan)。
---
## 4. 完成定义(DoD)
- 覆盖矩阵中所有 ❌/🟡 项补到 ✅,每条规则至少含正/反/边界;
- `node test/run.js` 全绿、退出码 0;
- 新增用例遵守测试纪律:不改正式代码去迁就测试、不软化断言、失败先用证据裁根因;
- 本计划与 `01-design合规逐节核对.md` 的结论一致(自动化测试成为人工审计的可回归证据)。
---
## 5. 当前状态小结
已覆盖(✅):§5.4 甩牌全套、§5.4.4 跟甩牌、§6.3 扣底倍数、§7 算子(常规+爬坡各档)、§8.1 算奖 get_chongguan、§10.1 位串/局数扣卡。共 89 项断言。
主要缺口(❌):**§5.1/5.2 正常跟牌毙牌垫牌(最大)**、§4 叫分坐庄/投降/暗牌/轮庄、§9 查牌门控/明牌、§7/§8 结算集成扩充、§6.3 扣底触发、§2/§3 专测、§12 端到端一局。缺口集中在 **L2 局内集成**与 **L3 RPC 层**,二者共需先搭 `_harness`/RPC 捕获脚手架(见 §3)。
+4
View File
@@ -22,6 +22,10 @@ node server/games/erqiwang/test/test_arith.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`:扣底倍数(§6.3)、算子常规+爬坡逐档(§7)、算奖 get_chongguan(§8.1)、甩牌分解/最大性/合法性(§5.4)、跟甩牌强制分量拆解(§5.4.4)。