【问题标题】:How to generate the same value for two different paths in spec?如何为规范中的两个不同路径生成相同的值?
【发布时间】:2018-09-30 02:12:10
【问题描述】:

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

我有一个 ::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))

【问题讨论】:

  • 我删除了自定义生成器,编写了一个检查函数并使用s/and 将其合并到父规范中。如果锤击足够,它的作品。知道是否有问题的答案仍然会很有趣。

标签: clojure clojure.spec


【解决方案1】:

您可以通过 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,并返回一个新的生成器,它将f 应用于从gen 生成的每个值。在这里,我们将::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 做同样事情的另一种方法,它允许更“自然”的let-like 语法:

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

【讨论】:

  • 太棒了! (我认为gen/let 直接来自clojure.test.check.generators,而不是clojure.spec.gen.alpha。是吗?)
  • @JeremyField 是的,规范动态加载/别名(部分) test.check 以便规范依赖者只需要 test.check 依赖关系,如果他们使用它:clojure.org/guides/spec#_project_setup
猜你喜欢
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-25
  • 1970-01-01
  • 2021-04-29
相关资源
最近更新 更多