我的数据有复杂的规格-如何生成样本?

6 specifications clojure

我的Clojure规格如下:

(spec/def ::global-id string?)
(spec/def ::part-of string?)
(spec/def ::type string?)
(spec/def ::value string?)
(spec/def ::name string?)
(spec/def ::text string?)
(spec/def ::date (spec/nilable (spec/and string? #(re-matches #"^\d{4}-\d{2}-\d{2}$" %))))
(spec/def ::interaction-name string?)
(spec/def ::center (spec/coll-of string? :kind vector? :count 2))
(spec/def ::context- (spec/keys :req [::global-id ::type]
                                :opt [::part-of ::center]))
(spec/def ::contexts (spec/coll-of ::context-))
(spec/def ::datasource string?)
(spec/def ::datasource- (spec/nilable (spec/keys :req [::global-id ::name])))
(spec/def ::datasources (spec/coll-of ::datasource-))
(spec/def ::location string?)
(spec/def ::location-meaning- (spec/keys :req [::global-id ::location ::contexts ::type]))
(spec/def ::location-meanings (spec/coll-of ::location-meaning-))
(spec/def ::context string?)
(spec/def ::context-association-type string?)
(spec/def ::context-association-name string?)
(spec/def ::priority string?)
(spec/def ::has-context- (spec/keys :req [::context ::context-association-type ::context-association-name ::priority]))
(spec/def ::has-contexts (spec/coll-of ::has-context-))
(spec/def ::fact- (spec/keys :req [::global-id ::type ::name ::value]))
(spec/def ::facts (spec/coll-of ::fact-))
(spec/def ::attribute- (spec/keys :req [::name ::type ::value]))
(spec/def ::attributes (spec/coll-of ::attribute-))
(spec/def ::fulltext (spec/keys :req [::global-id ::text]))
(spec/def ::feature- (spec/keys :req [::global-id ::date ::location-meanings ::has-contexts ::facts ::attributes ::interaction-name]
                                :opt [::fulltext]))
(spec/def ::features (spec/coll-of ::feature-))
(spec/def ::attribute- (spec/keys :req [::name ::type ::value]))
(spec/def ::attributes (spec/coll-of ::attribute-))
(spec/def ::ioi-slice string?)
(spec/def ::ioi- (spec/keys :req [::global-id ::type ::datasource ::features ::attributes ::ioi-slice]))
(spec/def ::iois (spec/coll-of ::ioi-))
(spec/def ::data (spec/keys :req [::contexts ::datasources ::iois]))
(spec/def ::data- ::data)
Run Code Online (Sandbox Code Playgroud)

但是它无法生成具有以下内容的样本:

(spec/fdef data->graph
  :args (spec/cat :data ::xml-spec/data-))

(println (stest/check `data->graph))
Run Code Online (Sandbox Code Playgroud)

那么它将无法生成,并带有以下异常: Couldn't satisfy such-that predicate after 100 tries.

自动生成规范非常方便,stest/check但是除了规范又如何还有生成器?

Tay*_*ood 8

当您Couldn't satisfy such-that predicate after 100 tries.从规范生成数据时看到错误时,常见的原因就是s/and规范,因为规范s/and仅基于第一个内部规范为规范构建生成器。

该规范似乎最有可能引起这种情况,因为in中的第一个内部规范/谓词s/andstring?,而以下谓词为正则表达式:

(s/def ::date (s/nilable (s/and string? #(re-matches #"^\d{4}-\d{2}-\d{2}$" %))))
Run Code Online (Sandbox Code Playgroud)

如果您对string?生成器进行采样,则会看到它生成的内容不太可能与您的正则表达式匹配:

(gen/sample (s/gen string?))
=> ("" "" "X" "" "" "hT9" "7x97" "S" "9" "1Z")
Run Code Online (Sandbox Code Playgroud)

test.check将尝试(默认情况下为100次)获取一个满足such-that条件的值,然后抛出您所看到的异常(如果不是)。

产生日期

您可以通过多种方式为此规范实现自定义生成器。这是一个test.check生成器,它将创建ISO本地日期字符串:

(def gen-local-date-str
  (let [day-range (.range (ChronoField/EPOCH_DAY))
        day-min (.getMinimum day-range)
        day-max (.getMaximum day-range)]
    (gen/fmap #(str (LocalDate/ofEpochDay %))
              (gen/large-integer* {:min day-min :max day-max}))))
Run Code Online (Sandbox Code Playgroud)

该方法获取有效时期的范围,用它来控制large-integer*生成器的范围,然后对生成的整数进行fmaps LocalDate/ofEpochDay处理。

(def gen-local-date-str
  (gen/fmap #(-> (Instant/ofEpochMilli %)
                 (LocalDateTime/ofInstant ZoneOffset/UTC)
                 (.toLocalDate)
                 (str))
            gen/large-integer))
Run Code Online (Sandbox Code Playgroud)

这从默认large-integer生成器开始,fmap用于提供一个函数,该函数java.time.Instant从生成的整数创建a ,将其转换为java.time.LocalDate,然后将其转换为恰好与您的日期字符串格式匹配的字符串。(在Java 9及更高版本中,使用会稍微简单一些java.time.LocalDate/ofInstant。)

另一种方法可能使用test.chuck的基于正则表达式的字符串生成器,或使用不同的日期类/格式器。请注意,我的两个示例都将生成-9999 / + 9999之前/之后的\d{4}年份,这与您的年份正则表达式不匹配,但是生成器应该经常产生令人满意的值,因此对于您的用例而言可能无关紧要。有很多方法可以生成日期值!

(gen/sample gen-local-date-str)
=>
("1969-12-31"
 "1970-01-01"
 "1970-01-01"
 ...)
Run Code Online (Sandbox Code Playgroud)

在规范中使用自定义生成器

然后,您可以使用s/with-gen以下命令将此生成器与您的规范相关联:

(s/def ::date
  (s/nilable
   (s/with-gen
    (s/and string? #(re-matches #"^\d{4}-\d{2}-\d{2}$" %))
    (constantly gen-local-date-str))))

(gen/sample (s/gen ::date))
=>
("1969-12-31"
 nil ;; note that it also makes nils b/c it's wrapped in s/nilable
 "1970-01-01"
 ...)
Run Code Online (Sandbox Code Playgroud)

如果您不想将定制生成器直接绑定到规范定义,则还可以为采用覆盖图的某些规范函数提供“独立”定制生成器:

(gen/sample (s/gen ::data {::date (constantly gen-local-date-str)}))
Run Code Online (Sandbox Code Playgroud)

使用此规范和生成器,我可以生成较大的::data规范,尽管由于某些集合规范,输出非常大。您还可以使用:gen-max规范中的选项控制生成过程中的大小。