Как включить функции clojure.spec'd в набор тестов
Есть ли способ включить функции clojure.spec в обобщенный набор тестов? Я знаю, что мы можем зарегистрировать спецификации и напрямую спецификации функций.
(ns foo
(:require [clojure.spec :as s]
[clojure.spec.test :as stest]))
(defn average [list-sum list-count]
(/ list-sum list-count))
(s/fdef average
:args (s/and (s/cat :list-sum float? :list-count integer?)
#(not (zero? (:list-count %))))
:ret number?)
А позже, если я захочу запустить генеративные тесты для этой функции, я могу использовать stest/check
,
=> (stest/check `average)
({:spec #object[clojure.spec$fspec_impl$reify__14282 0x68e9f37c "clojure.spec$fspec_impl$reify__14282@68e9f37c"], :clojure.spec.test.check/ret {:result true, :num-tests 1000, :seed 1479587517232}, :sym edgar.core.analysis.lagging/average})
Но я) есть ли способ включить эти тестовые прогоны в мой общий набор тестов? Я думаю о clojure.test
интеграция, которую имеет test.check. Самая близкая вещь, которую я могу видеть ii), является stest/instrument
(см. здесь) функция. Но это, кажется, просто позволяет нам включить проверку в repl. Не совсем то, что я хочу. Кроме того, iii) зарегистрированы ли функции?
(defspec foo-test
100
;; NOT this
#_(prop/for-all [v ...]
(= v ...))
;; but THIS
(stest/some-unknown-spec-fn foo))
2 ответа
Хорошо, решил это. Оказывается, нет решения из коробки. Но некоторые люди на слабом канале clojure-spec собрали defspec-test
решение для clojure.spec.test и clojure.test.
Итак, учитывая код в вопросе. Вы можете А) определить defspec-test
макрос, который берет имя вашего теста и список специальных функций. Затем вы можете использовать его в своем тестовом наборе.
Спасибо Clojure сообществу! И, надеюсь, такая полезная функция превращает ее в основную библиотеку.
A)
(ns foo.test
(:require [clojure.test :as t]
[clojure.string :as str]))
(defmacro defspec-test
([name sym-or-syms] `(defspec-test ~name ~sym-or-syms nil))
([name sym-or-syms opts]
(when t/*load-tests*
`(def ~(vary-meta name assoc
:test `(fn []
(let [check-results# (clojure.spec.test/check ~sym-or-syms ~opts)
checks-passed?# (every? nil? (map :failure check-results#))]
(if checks-passed?#
(t/do-report {:type :pass
:message (str "Generative tests pass for "
(str/join ", " (map :sym check-results#)))})
(doseq [failed-check# (filter :failure check-results#)
:let [r# (clojure.spec.test/abbrev-result failed-check#)
failure# (:failure r#)]]
(t/do-report
{:type :fail
:message (with-out-str (clojure.spec/explain-out failure#))
:expected (->> r# :spec rest (apply hash-map) :ret)
:actual (if (instance? Throwable failure#)
failure#
(:clojure.spec.test/val failure#))})))
checks-passed?#)))
(fn [] (t/test-var (var ~name)))))))
B)
(ns foo-test
(:require [foo.test :refer [defspec-test]]
[foo]))
(defspec-test test-average [foo/average])
Приведенный выше пример может потерпеть неудачу в случае, когда :failure
является false
из-за того, как stest/abbrev-result
тесты на провал. Смотрите CLJ-2246 для более подробной информации. Вы можете обойти это, определив свою собственную версию abbrev-result
, Также изменилось форматирование данных об ошибках.
(require
'[clojure.string :as str]
'[clojure.test :as test]
'[clojure.spec.alpha :as s]
'[clojure.spec.test.alpha :as stest])
;; extracted from clojure.spec.test.alpha
(defn failure-type [x] (::s/failure (ex-data x)))
(defn unwrap-failure [x] (if (failure-type x) (ex-data x) x))
(defn failure? [{:keys [:failure]}] (not (or (true? failure) (nil? failure))))
;; modified from clojure.spec.test.alpha
(defn abbrev-result [x]
(let [failure (:failure x)]
(if (failure? x)
(-> (dissoc x ::stc/ret)
(update :spec s/describe)
(update :failure unwrap-failure))
(dissoc x :spec ::stc/ret))))
(defn throwable? [x]
(instance? Throwable x))
(defn failure-report [failure]
(let [expected (->> (abbrev-result failure) :spec rest (apply hash-map) :ret)]
(if (throwable? failure)
{:type :error
:message "Exception thrown in check"
:expected expected
:actual failure}
(let [data (ex-data (get-in failure
[::stc/ret
:result-data
:clojure.test.check.properties/error]))]
{:type :fail
:message (with-out-str (s/explain-out data))
:expected expected
:actual (::s/value data)}))))
(defn check?
[msg [_ body :as form]]
`(let [results# ~body
failures# (filter failure? results#)]
(if (empty? failures#)
[{:type :pass
:message (str "Generative tests pass for "
(str/join ", " (map :sym results#)))}]
(map failure-report failures#))))
(defmethod test/assert-expr 'check?
[msg form]
`(dorun (map test/do-report ~(check? msg form))))
Вот немного измененная версия отличного ответа grzm, которая работает с [org.clojure/test.check "0.10.0-alpha4"]
. Он использует новый:pass?
ключ, полученный из этого PR: https://github.com/clojure/test.check/commit/09927b64a60c8bfbffe2e4a88d76ee4046eef1bc#diff-5eb045ad9cf20dd057f8344a877abd89R1184.
(:require [clojure.test :as t]
[clojure.string :as str]
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest])
(alias 'stc 'clojure.spec.test.check)
;; extracted from clojure.spec.test.alpha
(defn failure-type [x] (::s/failure (ex-data x)))
(defn unwrap-failure [x] (if (failure-type x) (ex-data x) x))
;; modified from clojure.spec.test.alpha
(defn abbrev-result [x]
(if (-> x :stc/ret :pass?)
(dissoc x :spec ::stc/ret)
(-> (dissoc x ::stc/ret)
(update :spec s/describe)
(update :failure unwrap-failure))))
(defn throwable? [x]
(instance? Throwable x))
(defn failure-report [failure]
(let [abbrev (abbrev-result failure)
expected (->> abbrev :spec rest (apply hash-map) :ret)
reason (:failure abbrev)]
(if (throwable? reason)
{:type :error
:message "Exception thrown in check"
:expected expected
:actual reason}
(let [data (ex-data (get-in failure
[::stc/ret
:shrunk
:result-data
:clojure.test.check.properties/error]))]
{:type :fail
:message (with-out-str (s/explain-out data))
:expected expected
:actual (::s/value data)}))))
(defn check?
[msg [_ body :as form]]
`(let [results# ~body
failures# (remove (comp :pass? ::stc/ret) results#)]
(if (empty? failures#)
[{:type :pass
:message (str "Generative tests pass for "
(str/join ", " (map :sym results#)))}]
(map failure-report failures#))))
(defmethod t/assert-expr 'check?
[msg form]
`(dorun (map t/do-report ~(check? msg form))))
Применение:
(deftest whatever-test
(is (check? (stest/check `whatever
;; optional
{:clojure.spec.test.check/opts {:num-tests 10000}})))