10

无论如何在通用测试套件中包含 clojure.spec'd 函数?我知道我们可以注册规格并直接指定功能

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

但是我)无论如何将这些测试运行包含在我的通用测试套件中?我正在考虑test.check 具有的那种clojure.test集成。我能看到的最接近ii)的是(see here ) 函数。但这似乎只是让我们打开对 repl 的检查。不是我想要的。另外,iii)功能规格是否已注册?stest/instrument

(defspec foo-test 
         100 

         ;; NOT this
         #_(prop/for-all [v ...]
           (= v ...))

         ;; but THIS
         (stest/some-unknown-spec-fn foo))
4

3 回答 3

9

好的,解决了这个。事实证明,没有开箱即用的解决方案。但是 clojure-spec 松弛频道上的一些人已经为clojure.spec.testclojure.testdefspec-test制定了解决方案。

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

感谢 Clojure 社区!!并且希望这样的实用功能能够使其成为核心库。

一种)

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

二)

(ns foo-test
  (:require [foo.test :refer [defspec-test]]
            [foo]))


(defspec-test test-average [foo/average])
于 2016-11-21T01:42:58.907 回答
3

上面的例子可能会失败,原因:failurefalse如何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))))
于 2018-01-16T02:43:56.657 回答
2

这是 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}})))
于 2019-04-24T23:00:17.250 回答