Как создать генератор test.check для последовательности действий в ограниченном порядке?
(require '[clojure.test.check.generators :as gen])
(def ACTIONS
{:create-new-user #{}
:edit-user #{:create-new-user}
:create-new-board #{:create-new-user}
:edit-board #{:create-new-board}
:create-new-anonymous-comment #{:create-new-board}
:create-new-signed-comment #{:create-new-board}
:edit-comment-text #{:create-new-anonymous-comment :create-new-signed-comment}
:edit-comment-text-and-flip-anonymity #{:create-new-anonymous-comment :create-new-signed-comment}
:flip-anonymity #{:create-new-anonymous-comment :create-new-signed-comment}
:vote-comment-up #{:create-new-anonymous-comment :create-new-signed-comment}
:vote-comment-down #{:create-new-anonymous-comment :create-new-signed-comment}})
(def actions (-> ACTIONS keys vec gen/elements gen/vector))
(defn filter-actions-into-logical-order [as]
(let [seen (atom #{})]
(filter
(fn [v]
(let [required (get ACTIONS v)
valid? (or (some? (some required @seen))
(and (empty? @seen) (= v :create-new-user)))]
(when valid?
(swap! seen conj v)
true)))
as)))
(def ordered-actions (gen/fmap #(-> % filter-actions-into-logical-order vec) actions))
В качестве примера двух генераторов:
# (last (gen/sample actions 100))
[:edit-user :vote-comment-down :flip-anonymity :vote-comment-down :vote-comment-down :vote-comment-up :edit-user :create-new-anonymous-comment :edit-board :create-new-signed-comment :vote-comment-up :edit-comment-text-and-flip-anonymity :edit-user :create-new-signed-comment :edit-user :edit-user :vote-comment-down :edit-user :vote-comment-down :create-new-user :vote-comment-down :create-new-user :create-new-user :edit-comment-text-and-flip-anonymity :create-new-user :edit-comment-text-and-flip-anonymity :create-new-anonymous-comment :edit-comment-text :create-new-board :vote-comment-down :flip-anonymity :create-new-signed-comment :vote-comment-up :create-new-user :create-new-signed-comment :edit-user :create-new-user :create-new-board :vote-comment-down :create-new-board :create-new-board :create-new-board :edit-board :edit-comment-text-and-flip-anonymity :edit-user :edit-comment-text :create-new-signed-comment :vote-comment-up :edit-comment-text-and-flip-anonymity :flip-anonymity :create-new-anonymous-comment :create-new-anonymous-comment :edit-board :create-new-signed-comment :edit-comment-text-and-flip-anonymity :edit-board :vote-comment-up :edit-comment-text :create-new-board :edit-comment-text-and-flip-anonymity :create-new-board :vote-comment-down :edit-comment-text-and-flip-anonymity :vote-comment-up :create-new-user :vote-comment-up :edit-comment-text :edit-board :edit-comment-text-and-flip-anonymity :flip-anonymity :edit-board :create-new-anonymous-comment :flip-anonymity :create-new-signed-comment :edit-user :edit-comment-text-and-flip-anonymity :edit-comment-text :edit-comment-text :create-new-user :flip-anonymity :edit-user :vote-comment-up :edit-user :create-new-user :edit-comment-text :edit-comment-text :flip-anonymity :edit-comment-text :edit-board :flip-anonymity :edit-board :edit-comment-text :edit-user :create-new-user :flip-anonymity]
# (last (gen/sample ordered-actions 100))
[:create-new-user :edit-user :edit-user :create-new-board :edit-board :edit-user :create-new-anonymous-comment :edit-comment-text :edit-board :edit-user :edit-user :vote-comment-up :edit-comment-text :create-new-signed-comment :edit-comment-text :create-new-board :edit-comment-text :edit-comment-text :edit-comment-text :vote-comment-up :vote-comment-up :edit-board :edit-comment-text-and-flip-anonymity :create-new-signed-comment :create-new-anonymous-comment :create-new-signed-comment :edit-user :create-new-anonymous-comment :edit-board :create-new-board :create-new-anonymous-comment :create-new-board :flip-anonymity :create-new-anonymous-comment :edit-board :vote-comment-up :vote-comment-down :edit-board :edit-comment-text :edit-user :edit-comment-text :flip-anonymity :create-new-signed-comment :vote-comment-up :edit-comment-text-and-flip-anonymity :vote-comment-up :create-new-signed-comment :edit-comment-text :create-new-signed-comment :create-new-anonymous-comment :edit-board :create-new-anonymous-comment]
ACTIONS
это карта, где ключом является имя действия, а значением является (на основе OR) зависимость для этого действия. Как пример, вы должны сначала :create-new-user
прежде чем что-то сделать, вы должны :create-new-board
прежде чем ты сможешь :edit-board
и у вас должен быть хотя бы один :create-new-*-comment
прежде чем ты сможешь :edit-comment-text
,
Код выше, кажется, работает, но это некрасиво. 1) мне не нравится как filter-actions-into-logical-order
код должен иметь конкретное исключение для :create-new-user
, 2) Мне не нравится, что я в основном беру список случайных действий и фильтрую его, пока действия не станут упорядоченными.
Мне интересно, как другие будут генерировать последовательность действий, как это с помощью test.check? Конечно, должен быть способ сделать это, используя только генераторы?
2 ответа
Это не так сложно сделать с помощью рекурсивного генератора, который использует gen/bind
(например, сначала сгенерируйте размер вашего списка действий, затем используйте рекурсию по этому размеру с gen/bind
так что у вас есть ранее сгенерированные действия на каждом шаге). Большим недостатком этого подхода является то, что gen/bind
работает довольно плохо с сокращением, и поэтому вы, вероятно, в конечном итоге получите длинные последовательности действий, которые практически не могут сжаться.
Я действительно хотел бы придумать что-то лучшее для этого.
Что ж, filter-into-logical-order
может быть чище с переключателем на reduce
(defn filter-into-logical-order [as]
(last (reduce
(fn [[seen accepted-as] action]
(let[needed (get ACTIONS action)]
(if (or (empty? needed) (some seen needed))
[(conj seen action) (conj accepted-as action)]
[seen accepted-as]))
)
[#{} []]
as)))
но это все еще дает около 30% пустых векторов для меня, и это не генератор. Я не уверен, что документированные комбинаторы действительно поддерживают то, что вы хотите. gen/vector
не поддерживает передачу вектора-в-конструкции его внутреннему генератору, поэтому он может знать, какие значения допустимы, что является структурой, необходимой вашим требованиям.