test.check 中的循环和状态管理

Loops & state management in test.check

随着 Spec 的引入,我尝试为我的所有函数编写 test.check 生成器。这对于简单的数据结构来说很好,但对于具有相互依赖的部分的数据结构来说往往会变得困难。换句话说,生成器内部需要一些状态管理。

拥有 Clojure 的 loop/recur 或 reduce 的生成器等价物已经有很大的帮助,因此在一次迭代中产生的值可以存储在某个聚合值中,然后可以在后续迭代中访问。

One simple example where this would be required, is to write a generator for splitting up a collection into exactly X partitions, with each partition having between zero and Y elements, and where the elements are then randomly assigned to any of the partitions. (Note that test.chuck's partition function does not allow to specify X or Y).

If you write this generator by looping through the collection, then this would require access to the partitions filled up during previous iterations, to avoid exceeding Y.

有人有什么想法吗?我找到的部分解决方案:

您可以通过将 gen/let(或等效的 gen/bind)与显式递归相结合来完成您所描述的迭代:

(defn make-foo-generator
  [state]
  (if (good-enough? state)
    (gen/return state)
    (gen/let [state' (gen-next-step state)]
      (make-foo-generator state'))))

但是,如果可能的话,值得尝试避免这种模式,因为每次使用 let/bind 都会破坏收缩过程。有时可以使用 gen/fmap 重新组织生成器。例如,要将一个集合划分为一系列 X 个子集(我意识到这与您的示例不完全相同,但我认为可以对其进行调整以适应),您可以这样做:

(defn partition
  [coll subset-count]
  (gen/let [idxs (gen/vector (gen/choose 0 (dec subset-count))
                             (count coll))]
    (->> (map vector coll idxs)
         (group-by second)
         (sort-by key)
         (map (fn [[_ pairs]] (map first pairs))))))