一份测试若总在成功路径上说 ok,却从未检查过拒绝时的错误流,破损解析器也可能继续通过。当前 t/ 目录覆盖核心输入、上下文、引用、Unicode、正则、模块版本、隔离 CSV 和帮助命令,调用真实 Perl 子进程分别比较退出码、STDOUT、STDERR;一条用错码位的断言还需要让测试框架明确失败。

2026-10-06 历史增量运行 prove -r examples/perl-language/t,当时报告 7 个文件、105 个断言,Result: PASS,命令退出 0,STDERR 空。反例 fixtures/deliberate-failure.t 故意要求 is 2, 3;单独运行 prove -v examples/perl-language/fixtures/deliberate-failure.t,STDOUT 中有 not ok 1、Result: FAIL,STDERR 标明 got 2、expected 3,退出码 1。该文件不放进默认的 t/:它是验收 TAP 失败通道的金标,不是被忽略的回归失败。

图:执行结果进入断言,再由 harness 汇总。

1
2
3
真实 CLI 子进程 -> (退出码、STDOUT 字节、STDERR 字节)
| 逐项 Test::More 断言 -> TAP 的 ok / not ok
+ prove 汇总文件数、断言数与最终进程状态

上游 Text::CSV 安装阶段的 52,723 项与这里 105 项都是历史阶段记录;当前累计测试数量由补全证据给出。上游包测试不能替代本工具测试,Hexo 生成也不能替代 Perl 行为。

破坏一个真实报表金标

fixtures/deliberate-report-gold.t 读取真实 demo 文件、调用实际解析器和报表函数,但把预期 TOTAL 写成 4。实际只有三条记录,因此这条断言必须失败,prove 退出 1。被破坏的是独立反例内的预期,不修改正常 golden/report.txt;反例单独运行,之后再次运行完整 t/,正常金标应通过。

1
2
3
cd examples/perl-language
prove -v fixtures/deliberate-report-gold.t
prove -lr t

两条命令的预期状态分别为非零和零,不能用 shell 最后一条状态抹掉前面的故意失败。completion/evidence/run.py 为每条命令保存 expected、实际 exit、stdout、stderr。异常退出、没有 TAP plan 和断言 not ok 是不同结果,harness 汇总应保留其区别。

测试中的 Perl 子进程使用 $^X,故意失败的 harness 也通过同一解释器加载 App::Prove。否则父进程来自构建树 5.38.2、子进程却从 PATH 选系统 5.34.1,再继承父进程的核心库路径,可能触发 XS ABI 不匹配。这个失败属于环境组成问题,不能通过删除 signatures/class 断言来修正。

反转执行顺序仍须给出同一结论

临时输入、拒绝文件、报表和锁文件都放在每个测试自己的临时目录中。课程回归正常顺序和倒序分别运行,保存各自完整结果;不能只重复同一顺序两次来证明没有顺序依赖。模块对象的状态独立、动态 local 恢复、注入故障后再次正常导入,也分别有机制断言。

环境变量同样可能形成隐含状态。子进程必须有明确解释器和模块路径,测试修改的条件应在局部作用域恢复;PERL_HASH_SEED 对照会重新启动进程,不依赖运行中改变变量去重置已经创建的哈希。locale 和编码属于另一个输入边界,ASCII 终端的显示不能代替字节比较。

[PATTERN] 先证明测试会拒绝错误,再用隔离与不同顺序验证成功结果。只收集成功日志无法证明回归能发现破坏。

练习:若删去“非零状态”断言只比较错误字符串,失败用状态 0 返回时测试能否及时报警?未必,所以拒绝路径必须同时检查三个通道。第二题:把故意失败反例搬进默认 t 目录再跳过它,是否等价?不等价,反例应独立执行并核对实际失败,不能把 skip 当作观察到 not ok。

验证 期望
正常累计回归 断言通过、退出 0
破坏报表金标 not ok、非零退出
倒序累计回归 与正常顺序同样通过
输入或输出失败 状态与错误流符合金标

历史原始失败输出见 evidence/25-failing-tap.*,本次在补全证据;参考 Test::More 1.302194、Test::Harness 3.44、Perl 5.38.2 pod/perlmod.pod(模块测试入口)。