我们假设我有一个生成器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
...)
如果我想为用户生成的单个用户序列生成单个动作,那么它很简单,只需直接生成/绑定用户 - gen到gen-action-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))))))
这种作品。问题是:
所以,回顾一下。我从一个生成器中获取一个生成的值,并将其作为参数传递给多个生成器。我如何以更有吸引力/更优雅的方式做到这一点?
答案 0 :(得分:2)
您可能想查看最新版本的test.check
(0.9.0
)。它现在在generator命名空间中包含一个let
,这使得组合生成器变得轻而易举:
这样做的缺点是你仍然无法直接在prop/for-all
中执行此操作(显然是由于向后兼容性原因)。
替代方案和我建议使用test.chuck(它由test.check
的当前维护者编写)。它有一个for-all
,其绑定表单就像generators/let
一样工作。这是我发现的最干净的方法,效果非常好。
答案 1 :(得分:1)
我会对您目前正在做的事情进行两项主要更改:
将您的内联gen/bind
拉出一个新的生成器,暂定名为users-and-actions-gen
(另请注意,我在结果中交换了users
和actions
的顺序匹配名称):
(def users-and-actions-gen
(gen/bind users-gen
(fn [users]
(gen/tuple
(gen/return users)
(gen/vector (user-actions-gen users))))))
不要使用原子来测试不需要它的东西。您可以生成一个懒惰的状态序列,并测试它们都具有您正在寻找的属性。这样它可以很好地读取,并具有您正在寻找的短路特性:
(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)确实是一个问题。