你写了一个Money值对象,测试通过,上线六个月。同事重构了add()方法,加了个零值短路优化,测试依然通过。然后生产环境开始出现0 EUR + 50 EUR = 0 EUR的账单——因为早返回路径直接返回了$this,没复制另一边的金额。
测试没错,只是不够。你只检查了随手输入的那一对数字,没验证加零是恒等操作,没验证操作顺序不影响结果,没验证一千对随机数字是否都符合预期。这个缺口,就是基于属性的测试(Property-Based Testing)要填补的。
打开网易新闻 查看精彩图片
传统示例测试的局限很明显:它只能固定你记得要考虑的行为。适合记录意图、捕获已知用例的回归,但找不到你从未想过的用例。像"货币加法满足交换律"这种领域规则,本质上不是说100+200成立,而是说每一对同币种Money值都成立。逐对写示例不可能,把规则写一次让框架自动试一千对,才是合理做法。
三类bug藏在示例的缝隙里:边界值(零、负数、PHP_INT_MAX、团队尚未支持的币种)、组合问题(对(a,b)有效但对(b,a)失效,或对长度为2的列表有效但对长度1或空列表失效)、往返一致性(序列化再反序列化,编码再解码,持久化再加载——示例测试用同一组fixture做两边,bug被掩盖因为往返从未遇到团队没想过的值)。
本文演示如何把Eris接入PHPUnit 11项目,为两个真实领域不变量(Money::add的交换律、OrderTotal的单调性)编写属性测试,并展示一个具体重构案例:属性测试捕获了一个示例测试漏了一年的bug。
热门跟贴