test.check: поведение в стиле let в свойствах / для всех

Следуя этому вопросу и сообщению в блоге, есть причина, почему prop/for-all не просто катиться в такой возможности напрямую? Например, что-то вроде:

(require '[clojure.test.check.generators :as gen])
(require '[clojure.test.check.properties :as prop])
(require '[clojure.test.check.clojure-test :refer :all])

(defspec some-props-test
  (prop/for-all [n (gen/choose 1 10)
                 v (gen/vector gen/int n) ;; treat n like its produced value
                 e (gen/element v)]
    ... do stuff with n, v & e ...
  ))

По сути, я хочу повторно использовать значение, полученное одним генератором в другом генераторе, а затем ссылаться на оба значения, полученные в реальном тестовом коде. Это существенно расширит сахар / магию for-all в разрешении ссылок на сгенерированные значения в блоке let-like, предоставляемом макросом, так как он работает в блоках выражений ниже.

Пожалуйста, дайте мне знать, если я упускаю что-то еще, что делает это возможным, или это просто не имеет смысла по какой-то причине для реализации.

2 ответа

Решение

Я согласен, что эта функциональность, вероятно, будет более полезной, чем что for-all в настоящее время делает. Основная причина, по которой он не был изменен, заключается в обратной совместимости (хотя, по общему признанию, код, использующий старый стиль, не сломался бы, он просто не уменьшился бы так, как раньше).

Но у вас есть больше возможностей, чем просто монады:

  • gen/let, который использует letв стиле связывания (это не вставная замена для for-all но вы можете использовать их вместе)
  • com.gfredericks.test.chuck.generators/for определено в вспомогательной библиотеке test.chuck - это как более модная версия gen/let
  • com.gfredericks.test.chuck.properties/for-allв той же библиотеке, которая является заменой для for-all

Я нашел более позднюю запись в блоге в этой серии, которая полностью очищает использование от test.check (нужно было немного прочитать monads первый, чтобы прогнать его). Итак, сначала можно объявить монаду:

(require '[clojure.algo.monads :as m])

(m/defmonad gen-m
    [m-bind gen/bind
     m-result gen/return])

(def vector-and-elem
  (m/domonad gen-m
    [n (gen/choose 1 10)
     v (gen/vector gen/int n)
     e (gen/element v)]
    [n v e]))

gen-m monad позволяет ссылаться на значение, которое будет сгенерировано для ранее объявленных генераторов.

Затем он может быть использован непосредственно в for-all вызов:

(defspec some-props-test
  (prop/for-all [[n v e] vector-and-elem]
    ... do stuff with n, v & e ...
  ))

Вы можете просто передать все значения, которые имеют отношение к вашему коду проверки ограничений, в выражении, созданном gen-m вызов монады через вектор (или карту, если хотите) и де-структурирование, чтобы получить то, что вам нужно.

Тем не менее, было бы неплохо, если бы это было сделано автоматически в for-all, но это работает достаточно хорошо.

Другие вопросы по тегам