5

假设我有一个生成器users-gen,它生成一组 1 个或多个用户。另一个称为参数化的生成器user-actions-gen,它接受一个或多个用户的序列,并生成这些用户可能执行的一系列操作。

(def user-gen 
 ;; generates a user
 ...)

(def users-gen 
 ;; sequences of 1 or more users 
 (gen/such-that not-empty (gen/vector gen/users))

(defn user-actions-gen [users]
  ;; a action performed by some user from the `users argument
  ...)

如果我想为 users-gen 生成的单个用户序列生成单个 action,那么很简单,直接 gen/bind users-gen 到 user-actions-gen 即可。

但是,我想从相同的用户序列中生成许多操作。我有这个问题,因为我基本上只是想说“这是状态,让任何随机动作进来,让我们将动作应用于状态,让我们确认状态仍然有效;对所有动作都这样做。 " 我有以下代码。

(defspec check-that-state-is-always-valid
  100
  (let [state-atm (atom {})]
    (prop/for-all
     [[actions users]
      (gen/bind users-gen
                (fn [users]
                  (gen/tuple
                   (gen/vector (user-actions-gen users))
                   (gen/return users))))]
     (doseq [action actions
             :let [state (swap! state-atm state-atm-transform-fx action)]]
       (is (state-still-valid? state))))))

这类作品。问题是:

  1. 它似乎完全评估了doseq,而不是在第一个错误时停止
  2. 只是看起来有点不对劲。代码到处都是,它的作用并不完全清楚。
  3. 似乎 user-actions-gen 应该采用 users-gen 的生成器,而不是 users-gen 的已实现用户价值?这对可组合性有帮助吗?请注意,我不想将它们放在一起,因为 users-gen 可能对其他生成器有用。

所以,回顾一下。我从一个生成器获取单个生成的值,并将其作为参数传递给多个生成器。我该如何以更有吸引力/优雅的方式来做这件事?

4

2 回答 2

2

您可能想查看test.check( 0.9.0) 的最新版本。它现在let在生成器命名空间中包含一个,这使得组合生成器变得轻而易举:

https://github.com/clojure/test.check/blob/master/src/main/clojure/clojure/test/check/generators.cljc#L1452

这样做的缺点是您仍然不能直接在其中执行此操作prop/for-all(显然是由于向后兼容的原因)。

另一种方法和我推荐的方法是使用test.chuck(无论如何它是由当前的维护者编写的test.check)。它确实具有for-all其绑定形式的工作generators/let方式。这是我发现的最干净的方法,并且效果很好。

于 2016-03-06T14:58:03.217 回答
1

我将对您目前正在做的事情进行两个主要更改:

  1. 将你的 inline 拉出gen/bind到一个新的生成器中,暂时命名users-and-actions-gen(另请注意,我在结果中交换了usersand的顺序actions以匹配名称):

    (def users-and-actions-gen
      (gen/bind users-gen
                (fn [users]
                  (gen/tuple
                   (gen/return users)
                   (gen/vector (user-actions-gen users))))))
    
  2. 不要使用原子来测试不需要它的东西。相反,您可以生成一个惰性状态序列,并测试它们是否都具有您正在寻找的属性。这样它就可以很好地阅读并具有您正在寻找的短路属性:

    (defspec check-that-state-is-always-valid
      100
      (prop/for-all [[users actions] users-and-actions-gen]
        (is (every? state-still-valid? 
                    (reductions (fn [state action]
                                  (state-atm-transform-fx state action))
                                {} actions)))))
    

除了这两个更改之外,我不确定您是否真的可以改进您正在做的事情。我认为你users-and-actions-gen是相当具体的。它可以稍微概括,但我不确定概括是否有用(它本质上是一个受限制的bind)。我上面提出的应该解决你的问题(1)和(2),但我不认为(3)真的是一个问题。

于 2014-06-07T12:54:41.287 回答