随着 Spec 的引入,我尝试为我的所有函数编写 test.check 生成器。这对于简单的数据结构来说很好,但对于具有相互依赖的部分的数据结构来说往往会变得困难。换句话说,然后需要在生成器中进行一些状态管理。
拥有 Clojure 的循环/递归或归约的生成器等效项已经非常有帮助,因此在一次迭代中产生的值可以存储在某个聚合值中,然后在后续迭代中可以访问。
需要这样做的一个简单示例是编写一个生成器,用于将集合拆分为恰好 X 个分区,每个分区具有零到 Y 之间的元素,然后将元素随机分配给任何分区。(注意
test.chuck
'partition
函数不允许指定 X 或 Y)。如果您通过循环遍历集合来编写此生成器,那么这将需要访问在先前迭代期间填充的分区,以避免超过 Y。
有人有什么想法吗?我发现的部分解决方案:
test.check
let
并bind
允许您生成一个值,然后稍后重用该值,但它们不允许迭代。tuple
您可以使用和函数的组合遍历先前生成的值的集合bind
,但这些迭代无权访问先前迭代期间生成的值。(defn bind-each [k coll] (apply tcg/tuple (map (fn [x] (tcg/bind (tcg/return x) k)) coll))
您可以使用原子(或挥发物)来存储和访问在先前迭代期间生成的值。这可行,但非常不符合 Clojure,特别是因为您需要
reset!
在返回生成器之前使用 atom/volatile,以避免它们的内容在生成器的下一次调用中被重用。生成器因其
bind
和return
函数而类似于 monad,这暗示了将诸如 Cats 之类的 monad 库与 State monad 结合使用。但是,State monad 在 Cats 2.0 中被删除(因为据称它不适合 Clojure),而我知道的其他支持库没有正式的 Clojurescript 支持。此外,在他自己的库中实现 State monad 时,Clojure 的 monad 专家之一 Jim Duey 似乎警告说 State monad 的使用与 test.check 的收缩不兼容(参见http://www. clojure.net/2015/09/11/Extending-Generative-Testing/),这大大降低了使用 test.check 的优点。