
Fox属性测试框架入门读懂QuickCheck思想告别手写测试用例【免费下载链接】FoxProperty Based Testing Library for Objective-C and Swift. QuickCheck for Apples Platforms.项目地址: https://gitcode.com/gh_mirrors/fo/FoxFox 是一款面向 Objective-C 和 Swift 的属性测试框架Property-Based Testing将 Haskell 经典的 QuickCheck 思想移植到了 Apple 平台。你不再需要手写几十个测试用例只需描述一条应当永远成立的性质Fox 就会自动生成数百份测试数据来轰炸你的代码——而且一旦发现失败还会自动把失败样本收缩Shrinking成最小的可复现案例。为什么手写测试用例总是不够用基于例子的测试Example-Based Testing是我们最熟悉的写法NSArray *sortedNumbers [MySorter sortNumbers:[5, 2, 1]]; XCTAssertEqualObjects(sortedNumbers, [1, 2, 5]);问题在于你只能测到你想得到的例子。空数组呢5 个元素重复值、负数呢一个排序函数可能要靠 20 个用例才能勉强覆盖边界而总有你想不到的角落。属性测试换了一个思路不写输入而是写应当恒成立的性质。对排序来说性质就是排序后每个元素都大于等于它前面的元素。剩下的交给 Fox。3 分钟安装 FoxCocoaPods 或 Git方式一CocoaPods推荐在测试目标的 Podfile 中加入一行pod Fox, ~ 1.0.1然后执行pod install即可。方式二Git 克隆git submodule add https://gitcode.com/gh_mirrors/fo/Fox.git Externals/Fox接着把Fox.xcodeproj添加到 Xcode 工程注意不是.xcworkspace再将 Fox-iOS 或 Fox-OSX 链接到你的测试 target。Fox 支持 iOS 7 / macOS 10.9启用 ARC采用 Apache 2.0 协议Objective-C API 已稳定Swift API 目前处于 alpha 阶段见Fox/Public/Generators/SwiftGenerators.swift。写第一个属性测试3 行核心代码以排序为例用 Fox 写出属性测试大致这样idFOXGenerator arraysOfIntegers FOXArray(FOXInteger()); FOXAssert(FOXForAll(arraysOfIntegers, ^BOOL(NSArray *integers) { NSArray *sortedNumbers [MySorter sortNumbers:integers]; // 性质排序结果应非递减 NSNumber *previousNumber nil; for (NSNumber *n in sortedNumbers) { if (previousNumber [previousNumber integerValue] [n integerValue]) { return NO; // 性质不成立 } previousNumber n; } return YES; }));用人话读一遍对于任意一个整数数组排序后必须满足非递减。关键就三件套组件作用FOXInteger()生成随机整数的生成器GeneratorFOXArray(...)由另一个生成器生成随机长度的数组FOXForAllFOXAssert声明此性质对所有数据恒成立不成立时抛出断言失败默认情况下Fox 会为每条性质跑500 组随机数据、数据最大尺寸为 200——相当于一条测试覆盖了 500 个你根本想不到的用例。核心魔法失败用例自动收缩 如果只是随机数据 性质校验那跟模糊测试Fuzzing差别不大。真正让 QuickCheck 思想发光的是Shrinking收缩。假设你的排序实现有 bug元素达到 5 个就会乱序。Fox 随机炸出的失败数据可能面目狰狞value that failed: (-3, -3, 1, -2, -7, -5)噪声满满。Fox 会自动尝试更小的变体逐个验证是否仍然失败[5, 9, 3, 5] // 仍然失败 [9, 3, 5] // 失败 [3, 5] // 通过 [9, 5] // 失败直到找到最小失败样例失败报告的第一行直接告诉你结论Property failed with: ( 0, 0, 0, 0, -1 ) smallest failing value: ( 0, 0, 0, 0, -1 )一眼看穿数组长度到 5、且尾部是-1时顺序被破坏。你不需要在一堆随机数据里人工扒信号。背后的机制是玫瑰树Rose Tree生成器产出的每个值都是树上一个节点子节点则是它的更小变体以整数 4 为例它的子节点是 0、2、3每个节点又继续分出更小的值。测试失败时Fox 沿树深度优先行走并逐一验证最终停在最小的失败值上。如果你要自定义收缩策略概念上的数据管线是这样的内置生成器FOXInteger、FOXArray、FOXString、FOXDictionary等组合使用即可免费获得收缩能力收缩方向非常直观整数向 0 收缩集合向空收缩。有状态 API用状态机测试一个队列 纯函数好测那像队列Queue这种有状态的 API 怎么办思路是把API 应该怎么工作建模成有限状态机——状态表示队列里现在有什么转移表示add / removeFox 基于这个状态机生成一串合法的 API 调用序列空队列上的 remove 会被前置条件自动排除再用后置条件校验每次调用的返回值。核心配置几行就说完了FOXFiniteStateMachine *stateMachine [[FOXFiniteStateMachine alloc] initWithInitialModelState:[]]; [stateMachine addTransition:[FOXTransition byCallingSelector:selector(addObject:) withGenerator:FOXInteger() nextModelState:^id(NSArray *modelState, id value) { return [modelState arrayByAddingObject:value]; }]]; // 生成一串合法调用在真实队列上执行并校验 idFOXGenerator executedCommands FOXExecuteCommands(stateMachine, ^id{ return [[Queue alloc] init]; }); FOXAssert(FOXForAll(executedCommands, ^BOOL(NSArray *commands) { return FOXExecutedSuccessfully(commands); }));如果实现有 bug比如悄悄吞掉了某个值Fox 给出的失败序列同样会被收缩成两三次调用[subject addObject:4] - (null) [subject removeObject] - (null) (Postcondition FAILED)对比原始那串 9 次 API 调用的失败现场最小复现一眼就能看懂加 4 再移除队列就不对劲了。想深入实现源码核心文件一览模块文件说明DSL 宏Fox/Public/FOXMacros.hFOXForAll、FOXAssert等入口核心生成器Fox/Public/Generators/FOXCoreGenerators.hFOXMap、FOXOneOf等组合工具数值生成器Fox/Public/Generators/FOXNumericGenerators.hFOXInteger、FOXFloat等状态机Fox/Public/Generators/FOXFiniteStateMachine.h有状态 API 建模玫瑰树Fox/Public/Data/FOXRoseTree.h收缩的数据结构测试执行器Fox/Public/FOXRunner.hseed、测试数、最大尺寸调参官方教程docs/source/tutorial.rst本文示例的完整版生成器参考docs/source/generators_reference.rst全部内置生成器清单小结写性质不写用例FOXForAll 生成器一条测试自动跑 500 组随机数据收缩是灵魂失败自动最小化调试成本直线下降有状态 API 也能测状态机生成合法的 API 调用序列。Fox 官方教程结尾引用了 Dijkstra 的名言也值得每一位测试者记住程序测试充其量只能证明错误存在而无法证明错误不存在。属性测试不能证明你的代码完美但它能让你比手写用例更接近完美。【免费下载链接】FoxProperty Based Testing Library for Objective-C and Swift. QuickCheck for Apples Platforms.项目地址: https://gitcode.com/gh_mirrors/fo/Fox创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考