What is generative testing in Clojure?
我遇到了
同时提供一些示例将非常有用。
作为入门阅读,我们获得了基本原理和概述以及指南,该指南应为您提供有关原因和方法的信息。
如果您想要一个稍微复杂的示例,我们可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | (defn string->semantic-version [version-string] "Create map representing the given version string. Returns nil if the string does not follow guidelines setforth by Semantic Versioning 2.0.0, http://semver.org/" ;; <MajorVersion>.<MinorVersion>.<PatchVersion>[-<Qualifier>][-SNAPSHOT] (if-let [[_ major minor patch qualifier snapshot] (re-matches #"(\\d+)\\.(\\d+)\\.(\\d+)(?:-(?!SNAPSHOT)([^\\-]+))?(?:-(SNAPSHOT))?" version-string)] (->> [major minor patch] (map #(Integer/parseInt %)) (zipmap [:major :minor :patch]) (merge {:qualifier qualifier :snapshot snapshot})))) |
它需要一个字符串,并尝试将其解析为表示某些工件的版本号的程序可读映射。其规格可能如下所示:
首先是一些依赖
1 2 3 4 5 6 7 | (ns leiningen.core.spec.util (:require [clojure.spec :as spec] [clojure.spec.gen :as gen] [miner.strgen :as strgen] [clojure.spec.test :as test] [leiningen.release :as release])) |
然后是一个辅助宏
1 2 3 4 5 6 7 8 | (defmacro stregex "Defines a spec which matches a string based on a given string regular expression. This the classical type of regex as in the clojure regex literal #\"\"" [string-regex] `(spec/with-gen (spec/and string? #(re-matches ~string-regex %)) #(strgen/string-generator ~string-regex))) |
然后是语义版本的定义
1 2 | (spec/def ::semantic-version-string (stregex #"(\\d+)\\.(\\d+)\\.(\\d+)(-\\w+)?(-SNAPSHOT)?")) |
和一些帮助规范
1 2 3 4 | (spec/def ::non-blank-string (spec/and string? #(not (str/blank? %)))) (spec/def ::natural-number (spec/int-in 0 Integer/MAX_VALUE)) |
用于在结果映射中定义键
1 2 3 4 5 | (spec/def ::release/major ::natural-number) (spec/def ::release/minor ::natural-number) (spec/def ::release/patch ::natural-number) (spec/def ::release/qualifier ::non-blank-string) (spec/def ::release/snapshot #{"SNAPSHOT"}) |
和地图本身
1 2 3 | (spec/def ::release/semantic-version-map (spec/keys :req-un [::release/major ::release/minor ::release/patch ::release/qualifier ::release/snapshot])) |
其次是功能说明:
1 2 3 | (spec/fdef release/string->semantic-version :args (spec/cat :version-str ::release/semantic-version-string) :ret ::release/semantic-version-map) |
到现在为止,我们可以让Clojure Spec生成测试数据并将其输入到函数本身中,以测试它是否符合我们为此设置的约束:
1 2 3 4 5 6 | (test/check `release/version-map->string) => ({:spec #object[clojure.spec$fspec_impl$reify__14248 0x16c2555"clojure.spec$fspec_impl$reify__14248@16c2555"], :clojure.spec.test.check/ret {:result true, :num-tests 1000, :seed 1491922864713}, :sym leiningen.release/version-map->string}) |
这告诉我们,在为我们生成的1000个测试用例规范中,该函数通过了每个函数。
您可能会发现最简单的方法是开始研究
1 2 3 4 5 6 7 8 9 10 | (require '[clojure.test.check :as tc]) (require '[clojure.test.check.generators :as gen]) (require '[clojure.test.check.properties :as prop]) (def sort-idempotent-prop (prop/for-all [v (gen/vector gen/int)] (= (sort v) (sort (sort v))))) (tc/quick-check 100 sort-idempotent-prop) ;; => {:result true, :num-tests 100, :seed 1382488326530} |
In prose, this test reads: for all vectors of integers, v, sorting v is equal to sorting v twice.
What happens if our test fails? test.check will try and find 'smaller' inputs that still fail. This process is called shrinking.
Let's see it in action:
1 2 3 4 5 6 7 8 9 | (def prop-sorted-first-less-than-last (prop/for-all [v (gen/not-empty (gen/vector gen/int))] (let [s (sort v)] (< (first s) (last s))))) (tc/quick-check 100 prop-sorted-first-less-than-last) ;; => {:result false, :failing-size 0, :num-tests 1, :fail [[3]], :shrunk {:total-nodes-visited 5, :depth 2, :result false, :smallest [[0]]}} |