这篇文章探讨了属性测试库当前令人遗憾的状态。作者通过历史回顾、现状调查和分析,指出尽管十五年前已出现先进的状态和并行测试技术,但当今大多数开源属性测试库仍缺失这些关键功能。文章提出了解决方案,并通过具体示例展示了如何以约400行代码简洁实现这些高级功能,同时倡导使用“假对象”代替状态机规范以降低使用门槛。
历史背景
属性测试源于90年代末的学术研究。John Hughes与Koen Claessen在查尔姆斯理工大学开发了开创性的QuickCheck库,其核心思想是“不写测试,生成测试”。该库最初只能测试纯函数。后续的商业化版本(Quviq QuickCheck)引入了两大突破性功能:
- 状态测试:基于状态机模型测试有状态系统。
- 并行测试:通过复用顺序状态机模型,结合线性一致性来检测并发系统中的竞态条件。
这些技术通过2006年至2009年间的一系列论文得以确立,但核心实现代码从未公开。
现状调查
作者对主流属性测试库(截至2024年7月)进行了调查,发现:
- 状态测试:虽然不少库提供了实验性或有限的支持,但实现质量参差不齐。
- 并行测试:支持的库寥寥无几,且多数实现存在问题或与经典方法不同。
许多库中用户请求添加这些特性的issue已开放多年,但进展缓慢。
现状分析
作者认为这种“悲伤”现状主要源于三个原因:
- 实用性认知:观点认为测试纯函数片段已能获得大部分收益,状态和并行测试的边际效益不高。
- 培训需求:编写状态机模型需要不同的思维方式,需要专门培训,而开源项目通常缺乏配套的培训资源。
- 商业模式:Quviq采取闭源并配套培训咨询服务的商业模式,被认为比单纯开源更利于技术在工业界的落地和持续改进。
作者对此提出了反驳:状态和并行测试对许多工业系统至关重要;纯属性测试同样需要培训;闭源模式虽可能助力商业采纳,却阻碍了技术在开源社区的广泛传播和复现。
解决方案与实现
作者提出两个方向:
- 提供精简的开源实现:旨在打破技术壁垒,促进普及。
- 简化规范:用程序员更熟悉的“假对象”替代形式化的状态机规范,降低学习成本。
文章的核心部分展示了一个约400行代码的库实现,其关键设计包括:
- 核心接口:用户通过实现
StateModel类型类来定义待测系统的状态、命令、响应以及一个纯函数的“假对象”实现(runFake)和一个执行真实系统操作的runReal。
- 生成与收缩:自动生成命令序列,并在测试失败时智能收缩以找到最小反例。
- 执行与断言:执行命令序列,对比真实系统与“假对象”在相同输入下的输出是否一致。
- 并行扩展:
ParallelModel接口复用顺序模型,通过生成并行命令树(Fork)并检查执行历史的线性一致性来发现竞态条件。
示例演示
作者通过一系列示例验证该方法:
- 纯函数与状态测试:计数器、环形缓冲器(修复了其C实现的多处错误)、《虎胆龙威3》水壶谜题(将状态测试用作路径探索模型检查器)。
- 并行测试:并发计数器(发现了竞态条件)和进程注册表(模拟Erlang进程注册表,发现了复杂的竞态条件)。演示中通过引入微小延迟来改善反例收缩效果。
- 集成测试:展示了如何将经过契约测试的“假对象”用于快速、确定性的集成测试。以队列和文件系统为例,说明了如何定义接口、实现真实与假对象版本,并利用假对象进行组件测试。这种方法可扩展至更复杂的系统,其中每个组件的假对象都经过自身的状态/并行属性测试来保证其正确性。
结论
作者总结,通过借鉴原始QuickCheck的简洁实现精神,并结合更易理解的“假对象”规范,完全有可能在开源世界中复兴高级属性测试技术。文章提供了完整的实现思路和代码示例,旨在激发社区在不同编程语言中进行实践和探索。