正确嵌套Clojure规格?

时间:2018-07-12 17:31:51

标签: clojure clojure.spec

我认为我可能对函数中的嵌套规范的正确顺序有疑问-特别是s/with-gens/or ...

我具有此功能和规范:

(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
  (if (nil? g-hist)
    [{:giver :none, :givee :none}]
    (assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
        :args (s/with-gen
                (s/or :input-hist (s/and
                                    (s/cat :g-hist :unq/gift-history
                                           :g-year (s/and int? #(> % -1))
                                           :g-pair :unq/gift-pair)
                                    #(<= (:g-year %) (count (:g-hist %))))
                      :input-nil (s/and
                                   (s/cat :g-hist nil?
                                          :g-year (s/and int? #(> % -1))
                                          :g-pair :unq/gift-pair)
                                   #(<= (:g-year %) (count (:g-hist %)))))
                #(gen/let [hist (s/gen :unq/gift-history)
                           year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
                           pair (s/gen :unq/gift-pair)]
                   [hist year pair]))
        :ret :unq/gift-history)

哪些测试正确:

(stest/check `set-gift-pair-in-gift-history)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
                0x729d93b6
                "clojure.spec.alpha$fspec_impl$reify__2451@729d93b6"],
  :clojure.spec.test.check/ret {:result true,
                                :num-tests 1000,
                                :seed 1531413555637},
  :sym clojure-redpoint.roster/set-gift-pair-in-gift-history})

其参数正确符合:

(s/conform (s/or :input-hist (s/and
                               (s/cat :g-hist :unq/gift-history
                                      :g-year (s/and int? #(> % -1))
                                      :g-pair :unq/gift-pair)
                               #(<= (:g-year %) (count (:g-hist %))))
                 :input-nil (s/and
                              (s/cat :g-hist nil?
                                     :g-year (s/and int? #(> % -1))
                                     :g-pair :unq/gift-pair)
                              #(<= (:g-year %) (count (:g-hist %)))))
           [[{:giver :GeoHar, :givee :JohLen}] 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-hist
 {:g-hist [{:giver :GeoHar, :givee :JohLen}],
  :g-year 0,
  :g-pair {:giver :RinStaXX, :givee :PauMccXX}}]

(s/conform (s/or :input-hist (s/and
                               (s/cat :g-hist :unq/gift-history
                                      :g-year (s/and int? #(> % -1))
                                      :g-pair :unq/gift-pair)
                               #(<= (:g-year %) (count (:g-hist %))))
                 :input-nil (s/and
                              (s/cat :g-hist nil?
                                     :g-year (s/and int? #(> % -1))
                                     :g-pair :unq/gift-pair)
                              #(<= (:g-year %) (count (:g-hist %)))))
           [nil 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-nil
 {:g-hist nil, :g-year 0, :g-pair {:giver :RinStaXX, :givee :PauMccXX}}]

但是当此“正确”功能被第二个功能消耗时:

(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
  (let [plr (get-player-in-roster plrs-map plr-sym)
        gh (get-gift-history-in-player plr)
        ngh (set-gift-pair-in-gift-history gh g-year g-pair)
        nplr (set-gift-history-in-player ngh plr)]
    (assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
        :args (s/cat :plrs-map ::plr-map
                     :plr-sym keyword?
                     :g-year (s/and int? #(> % -1))
                     :g-pair :unq/gift-pair)
        :ret ::plr-map)

消费函数成为消费函数的错误源(nil情况-我认为已经处理过):

(stest/check `set-gift-pair-in-roster)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
                0x3bbc704a
                "clojure.spec.alpha$fspec_impl$reify__2451@3bbc704a"],
  :clojure.spec.test.check/ret {:result #error{:cause "Call to #'clojure-redpoint.roster/set-gift-pair-in-gift-history did not conform to spec:
                                                       In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
                                                       val: {:g-hist nil, :g-year 1, :g-pair {:givee :_+, :giver :RJK/Y24}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))

我尝试更改使用功能中规格的顺序和分组(嵌套)-但随后它甚至无法测试使用功能,但未能通过它过去通过的测试。

对这里出什么问题有任何想法吗?

谢谢!

编辑:

如所建议的,以下是完整的代码,可以使您更好地理解:

;Here is an example of The Beatles keeping track of the Xmas gifts
;they give to each other (:giver and :givee) each year over time:

(ns clojure-redpoint.roster2
  (:require [clojure.spec.alpha :as s]
            [orchestra.spec.test :as st]
            [clojure.test.check.generators :as gen]
            [clojure.spec.test.alpha :as stest]))

(s/def ::givee keyword?)
(s/def ::giver keyword?)
(s/def :unq/gift-pair (s/keys :req-un [::givee ::giver]))
(s/def ::name string?)
(s/def :unq/gift-history (s/coll-of :unq/gift-pair :kind vector?))
(s/def :unq/player (s/keys :req-un [::name :unq/gift-history]))
(s/def ::plr-map (s/map-of keyword? :unq/player))


(defn- get-player-in-roster [plrs-map plr-sym]
  (get plrs-map plr-sym))
(s/fdef get-player-in-roster
        :args (s/cat :plrs-map ::plr-map :plr-sym keyword?)
        :ret (s/or :found :unq/player
                   :not-found nil?))

(defn- get-gift-history-in-player [plr]
  (get plr :gift-history))
(s/fdef get-gift-history-in-player
        :args (s/or :input-plr (s/cat :plr :unq/player)
                    :input-nil (s/cat :plr nil?))
        :ret (s/or :found :unq/gift-history
                   :not-found nil?))

(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
  (if (nil? g-hist)
    [{:giver :none, :givee :none}]
    (assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
        :args (s/with-gen
                (s/or :input-hist (s/and
                                    (s/cat :g-hist :unq/gift-history
                                           :g-year (s/and int? #(> % -1))
                                           :g-pair :unq/gift-pair)
                                    #(<= (:g-year %) (count (:g-hist %))))
                      :input-nil (s/and
                                   (s/cat :g-hist nil?
                                          :g-year (s/and int? #(> % -1))
                                          :g-pair :unq/gift-pair)
                                   #(<= (:g-year %) (count (:g-hist %)))))
                #(gen/let [hist (s/gen :unq/gift-history)
                           year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
                           pair (s/gen :unq/gift-pair)]
                   [hist year pair]))
        :ret :unq/gift-history)

(defn set-gift-history-in-player [g-hist plr]
  (if (or (nil? g-hist) (nil? plr))
    {:name "none", :gift-history [{:giver :none, :givee :none}]}
    (assoc plr :gift-history g-hist)))
(s/fdef set-gift-history-in-player
        :args (s/or :input-good (s/cat :g-hist :unq/gift-history
                                       :plr :unq/player)
                    :input-hist-nil (s/cat :g-hist nil?
                                           :plr :unq/player)
                    :input-plr-nil (s/cat :g-hist :unq/gift-history
                                          :plr nil?)
                    :input-both-nil (s/cat :g-hist nil?
                                           :plr nil?))
        :ret :unq/player)

(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
  (let [plr (get-player-in-roster plrs-map plr-sym)
        gh (get-gift-history-in-player plr)
        ngh (set-gift-pair-in-gift-history gh g-year g-pair)
        nplr (set-gift-history-in-player ngh plr)]
    (assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
        :args (s/cat :plrs-map ::plr-map
                     :plr-sym keyword?
                     :g-year (s/and int? #(> % -1))
                     :g-pair :unq/gift-pair)
        :ret ::plr-map)

(st/instrument)

(def roster-map
  {:RinSta {:name "Ringo Starr", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
   :JohLen {:name "John Lennon", :gift-history [{:giver :JohLen, :givee :GeoHar}]},
   :GeoHar {:name "George Harrison", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
   :PauMcc {:name "Paul McCartney", :gift-history [{:giver :PauMcc, :givee :RinSta}]}})

(s/conform ::plr-map
           (set-gift-pair-in-roster roster-map :PauMcc 0 {:giver :JohLenXXX, :givee :GeoHarXXX}))
;=>
;{:RinSta {:name "Ringo Starr", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
; :JohLen {:name "John Lennon", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
; :GeoHar {:name "George Harrison",
;          :gift-history [{:giver :PauMcc, :givee :RinSta}]},
; :PauMcc {:name "Paul McCartney",
;          :gift-history [{:giver :JohLenXXX, :givee :GeoHarXXX}]}}

;(stest/check `set-gift-pair-in-roster)

不幸的是,它没有帮助我发现我的错误...

1 个答案:

答案 0 :(得分:2)

问题是,当您set-gift-pair-in-gift-history时,使用无效参数调用了您所检测的函数(stest/check `set-gift-pair-in-roster)

CompilerException clojure.lang.ExceptionInfo: Call to #'playground.so/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :A, :giver :A}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))

check输出为我们提供了最少的输入来重现错误:

(set-gift-pair-in-roster {} :A 1 {:givee :A, :giver :A})

我们可以看到失败函数的第一个参数为nil。查看set-gift-pair-in-gift-history的功能规范,有一个可疑的规范涵盖了这种情况:

            :input-nil (s/and
                         (s/cat :g-hist nil?
                                :g-year (s/and int? #(> % -1))
                                :g-pair :unq/gift-pair)
                         #(<= (:g-year %) (count (:g-hist %)))))

这仅在g-hist为零且g-year0时才符合,但是:g-year的生成器将生成除0之外的许多数字。这就是为什么instrument编辑后对其调用失败的原因。

instrumentcheck指定的行为方式与实际行为之间显示出差异。我将首先考虑如何在第一个参数为nil时指定set-gift-pair-in-gift-history。当第一个arg为nil时,该实现不关心其他参数,因此您可以调整函数规格以反映这一点:

:input-nil (s/cat :g-hist nil? :g-year any? :g-pair any?)

进行此更改后,您的顶级功能应成功check