如何为规范中的两个不同路径生成相同的值?

How to generate the same value for two different paths in spec?

我正在尝试学习如何将 overridess/gen 结合使用。

我有一张 ::parent 地图,其中包含一张 ::child 地图。 parent 和 child 都有共同的密钥。 要求是键在parent和child之间具有相同的值,例如{:a 1 :b 2 :child {:a 1 :b 2}。我知道这似乎是多余的,但问题域需要它。

下面的代码生成示例,但不满足上面的要求

有没有办法在两个位置使用相同的生成值?

(ns blah
  (:require [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as gen])) 

(s/def ::a (s/int-in 1 5))
(s/def ::b (s/int-in 1 6))

(s/def ::child
  (s/keys :req-un [::a ::b]))

(defn- parent-gen []
  (let [a #(s/gen ::a)
        b #(s/gen ::b)]
    (s/gen ::parent-nogen
           ; overrides map follows
           {::a a ::b b
            ::child #(s/gen ::child
                            ; another overrides map
                            {::a a ::b b})))

(s/def ::parent-nogen
  (s/keys :req-un [::a ::b ::child]))

(s/def ::parent
  (s/with-gen ::parent-nogen parent-gen))

(gen/sample (s/gen ::parent))

您可以使用 test.check 的 fmap:

(s/def ::a (s/int-in 1 5))
(s/def ::b (s/int-in 1 6))
(s/def ::child (s/keys :req-un [::a ::b]))
(s/def ::parent (s/keys :req-un [::a ::b ::child]))
(gen/sample
  (s/gen ::parent
         {::parent ;; override default gen with fmap'd version
          #(gen/fmap
            (fn [{:keys [a b child] :as p}]
              (assoc p :child (assoc child :a a :b b)))
            (s/gen ::parent))}))
=>
({:a 1, :b 2, :child {:a 1, :b 2}}
 {:a 2, :b 2, :child {:a 2, :b 2}}
 {:a 1, :b 1, :child {:a 1, :b 1}}
 {:a 3, :b 2, :child {:a 3, :b 2}}
 {:a 2, :b 4, :child {:a 2, :b 4}}
 {:a 4, :b 4, :child {:a 4, :b 4}}
 {:a 3, :b 3, :child {:a 3, :b 3}}
 {:a 4, :b 4, :child {:a 4, :b 4}}
 {:a 3, :b 4, :child {:a 3, :b 4}}
 {:a 3, :b 4, :child {:a 3, :b 4}})

fmap 接受一个函数 f 和一个生成器 gen,以及 returns 一个新的生成器,它将 f 应用于从 [= 生成的每个值16=]。在这里,我们将 ::parent 的默认生成器和一个函数传递给它,该函数采用这些父映射并将适当的键复制到 :child 映射中。

如果您希望此规范强制执行该相等性(除了生成之外),您需要将 s/and 添加到 ::parent 规范并使用谓词来检查:

(s/def ::parent
  (s/and (s/keys :req-un [::a ::b ::child])
         #(= (select-keys % [:a :b])
             (select-keys (:child %) [:a :b]))))

编辑:这是用 gen/let 做同样事情的另一种方法,允许更像 "natural" let 的语法:

(gen/sample
  (gen/let [{:keys [a b] :as parent} (s/gen ::parent)
            child (s/gen ::child)]
    (assoc parent :child (assoc child :a a :b b))))