Haskell中Hedgehog库Gen与forAll生成随机输入的差异及结合性测试
嘿,我来帮你把Sum类型的结合性测试补全,再把Gen和forAll的差异讲得明明白白~
1. 完成Sum类型的结合性测试
首先把你没写完的测试代码补全,完整的可运行实现如下:
import Hedgehog import qualified Hedgehog.Gen as Gen import Data.Monoid (Sum(..)) -- 生成三个Int的三元组,利用Gen的Applicative实例组合生成器 genTriple :: Gen (Int, Int, Int) genTriple = liftA3 (,,) Gen.enumBounded Gen.enumBounded Gen.enumBounded -- 测试Sum类型的结合性:a <> (b <> c) ≡ (a <> b) <> c prop_assoc :: Property prop_assoc = property $ do (a, b, c) <- forAll genTriple let leftAssoc = Sum a <> (Sum b <> Sum c) rightAssoc = (Sum a <> Sum b) <> Sum c leftAssoc === rightAssoc
几个小细节提醒:
- 别忘了导入
Data.Monoid里的Sum类型,以及Hedgehog的核心模块和Gen子模块 - 用Hedgehog提供的
===操作符做等价性断言,它会在测试失败时自动输出两边的差异 - 如果你不想测试所有Int值(毕竟范围太大),可以把
Gen.enumBounded换成自定义范围的生成器,比如Gen.int (Range.constant (-1000) 1000)来限定数值范围
2. Gen和forAll的核心差异
这俩完全是测试流程里的不同角色,根本不在一个层面上:
Gen:数据生成的“蓝图”
Gen是用来描述如何生成随机测试数据的类型,它本身只是一个“配方”,不会实际生成任何数据。比如你写的genTriple就是一个Gen (Int, Int, Int),它定义了“通过三个枚举所有Int的生成器,组合出三元组”的规则。
你可以把Gen理解成一个函数的定义——它告诉Hedgehog生成数据的逻辑,但只有被调用时才会干活。forAll:执行蓝图并获取数据
forAll是Hedgehog测试上下文(PropertyTmonad)里的操作,它的作用是拿着你定义好的Gen生成器,实际生成随机测试数据,然后把数据绑定到变量里供测试使用。
简单说:Gen负责“说清楚要生成什么”,forAll负责“真的生成出来给你用”。
举个更直观的例子:
- 你写
genInt :: Gen Int = Gen.enumBounded,这只是声明了“我要生成所有可能的Int”,但此时没有任何Int被创建。 - 当你在
property $ do块里写x <- forAll genInt,Hedgehog才会按照genInt的规则生成一个随机Int,把它绑定给x,你接下来就能用x编写测试逻辑了。
另外,forAll还有个超实用的特性:如果测试失败,它会自动**缩小(shrink)**生成的数据,帮你找到最小的能触发失败的输入——而这个缩小策略是和Gen绑定的,每个Gen都自带对应的缩小逻辑(比如Gen.enumBounded的缩小会逐步往0靠近)。
内容的提问来源于stack exchange,提问作者Shersh
相关产品推荐
相关产品推荐

