Как включить функции 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}})))
Другие вопросы по тегам