如何在测试套件中包含clojure.spec'd函数

时间:2016-11-19 20:58:49

标签: unit-testing clojure generative-testing

无论如何在广义测试套件中包含clojure.spec'd函数?我知道我们可以register specs直接spec functions

(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})

i) 无论如何都要在我的通用测试套件中包含这些测试运行?我在想clojure.test stest/instrument整合test.check has。我能看到的最接近的是 ii) (defspec foo-test 100 ;; NOT this #_(prop/for-all [v ...] (= v ...)) ;; but THIS (stest/some-unknown-spec-fn foo)) (请参阅here)函数。但这似乎只是让我们开始检查repl。不是我想要的。此外, iii) 是否已注册功能规格?

{{1}}

3 个答案:

答案 0 :(得分:9)

好的,解决了这个问题。事实证明,没有开箱即用的解决方案。但是一些关于clojure规范松弛频道的人已经为 clojure.spec.test clojure.test 组合了一个defspec-test解决方案。

所以考虑到问题中的代码。您可以 A)定义带有您的测试名称和规范函数列表的defspec-test宏。然后,您可以 B)在测试套件中使用它。

谢谢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])

答案 1 :(得分:2)

由于:failure如何测试失败,上述examplefalsestest/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))))

答案 2 :(得分:1)

这是grzm出色答案的略微修改版本,可与[org.clojure/test.check "0.10.0-alpha4"]一起使用。它使用来自此PR的新:pass?密钥: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}})))
相关问题