Clojure 中的生成式测试是什么?
What is generative testing in Clojure?
我遇到了 Generative Testing in Clojure with spec
概念并想了解它。
另外提供一些例子会很有用。
作为介绍性读物,我们有 Rationale and Overview along with the Guide 它应该为您提供有关原因和方法的信息。
如果您想要一个稍微复杂的示例,我们可以采用 leiningen.release
的 string->semantic-version
函数:
(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}))))
它获取一个字符串并尝试将其解析为程序可读的映射,表示某个工件的版本号。它的规格可能如下所示:
首先是一些依赖关系
(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]))
然后是辅助宏
(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)))
后面是语义版本的定义
(spec/def ::semantic-version-string
(stregex #"(\d+)\.(\d+)\.(\d+)(-\w+)?(-SNAPSHOT)?"))
和一些助手规格
(spec/def ::non-blank-string
(spec/and string? #(not (str/blank? %))))
(spec/def ::natural-number
(spec/int-in 0 Integer/MAX_VALUE))
用于定义生成的映射中的键
(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"})
和地图本身
(spec/def ::release/semantic-version-map
(spec/keys :req-un [::release/major ::release/minor ::release/patch
::release/qualifier ::release/snapshot]))
后面是函数说明:
(spec/fdef release/string->semantic-version
:args (spec/cat :version-str ::release/semantic-version-string)
:ret ::release/semantic-version-map)
现在我们可以让 Clojure Spec 生成测试数据并将其提供给函数本身,以测试它是否满足我们为它设置的约束:
(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 个测试用例规范中,函数通过了每个测试用例。
您可能会发现在深入了解 Clojure Spec
之前先查看 clojure/test.check
最容易。 From the project page:
(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:
(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]]}}
我遇到了 Generative Testing in Clojure with spec
概念并想了解它。
另外提供一些例子会很有用。
作为介绍性读物,我们有 Rationale and Overview along with the Guide 它应该为您提供有关原因和方法的信息。
如果您想要一个稍微复杂的示例,我们可以采用 leiningen.release
的 string->semantic-version
函数:
(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}))))
它获取一个字符串并尝试将其解析为程序可读的映射,表示某个工件的版本号。它的规格可能如下所示:
首先是一些依赖关系
(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]))
然后是辅助宏
(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)))
后面是语义版本的定义
(spec/def ::semantic-version-string
(stregex #"(\d+)\.(\d+)\.(\d+)(-\w+)?(-SNAPSHOT)?"))
和一些助手规格
(spec/def ::non-blank-string
(spec/and string? #(not (str/blank? %))))
(spec/def ::natural-number
(spec/int-in 0 Integer/MAX_VALUE))
用于定义生成的映射中的键
(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"})
和地图本身
(spec/def ::release/semantic-version-map
(spec/keys :req-un [::release/major ::release/minor ::release/patch
::release/qualifier ::release/snapshot]))
后面是函数说明:
(spec/fdef release/string->semantic-version
:args (spec/cat :version-str ::release/semantic-version-string)
:ret ::release/semantic-version-map)
现在我们可以让 Clojure Spec 生成测试数据并将其提供给函数本身,以测试它是否满足我们为它设置的约束:
(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 个测试用例规范中,函数通过了每个测试用例。
您可能会发现在深入了解 Clojure Spec
之前先查看 clojure/test.check
最容易。 From the project page:
(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:
(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]]}}