Как мне скомпоновать генераторы clojure.test.check по принципу "один ко многим"?
Допустим, у меня есть генератор 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, то это просто, просто gen / привязать 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))))))
Такого рода работы. Проблемы в том, что:
- Кажется, чтобы полностью оценить, а не останавливаться на первой ошибке
- Это выглядит немного не так. Код повсюду, не совсем очевидно, что он делает.
- Кажется, что, возможно, user-actions-gen должен брать генератор users-gen, а не реализованную пользовательскую ценность users-gen? Поможет ли это с компоновкой? Обратите внимание, что я не хочу собирать их вместе, так как users-gen, вероятно, полезен для других генераторов.
Итак, подведем итоги. Я беру одно сгенерированное значение из одного генератора и передаю его в качестве аргумента нескольким генераторам. Как мне сделать это более привлекательным / элегантным способом?
2 ответа
Вы можете проверить новейшую версию test.check
(0.9.0
). Теперь он включает в себя let
в пространстве имен генераторов, что упрощает создание генераторов:
Недостатком этого является то, что вы все еще не можете просто сделать это прямо в prop/for-all
(видимо из-за обратной совместимости).
Альтернатива, и я бы порекомендовал использовать test.chuck (он написан текущим сопровождающим test.check
тем не мение). У него есть for-all
чьи обязательные формы работают так же, как generators/let
делает. Это самый чистый подход, который я нашел и работает довольно хорошо.
Я бы сделал два основных изменения в том, что вы сейчас делаете:
Вытащите свой встроенный
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) действительно проблема.