6

我遇到Generative Testing in Clojure with spec的概念,并希望了解它。什么是Clojure中的生成测试?

还提供一些例子将是非常有用的。

+0

也发现了这个:http://dev.solita.fi/2017/04/10/making-software-testing-easier-with-clojure.html –

回答

11

作为介绍性阅读,我们已获得Rationale and Overview以及Guide,它应该为您提供有关原因和方式的信息。

如果您想稍微复杂的例子,我们可以采取的leiningen.releasestring->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生成测试数据并将其馈入函数本身,以测试它是否符合co nstraints我们已经把弥补了这一点:

(test/check `release/version-map->string) 
=> ({:speC#object[clojure.spec$fspec_impl$reify__14248 0x16c2555 "[email protected]"], 
    :clojure.spec.test.check/ret {:result true, 
            :num-tests 1000, 
            :seed 1491922864713}, 
    :sym leiningen.release/version-map->string}) 

这就告诉我们,在对1000个测试用例对我们规范生成的函数传递的每一个。

+0

大回答。你可以谈谈更多关于辅助宏的需求,而不是仅仅使用函数来实现同样的事情? –

+0

助手宏只存在,因为我在我的真实世界代码中多次重复该代码。在上面的例子中,除了可读性以外,实际上没有任何意义。 – Rovanion

6

在潜入Clojure Spec之前,您可能最容易开始寻找clojure/test.checkFrom 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} 

在散文,该试验如下:对于整数的所有向量,V,排序v等于排序v两次。

如果我们的测试失败会发生什么? test.check会尝试找到仍然失败的“较小”输入。这个过程被称为收缩。 让我们来看看它在行动:

(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]]}} 
+0

埃里克诺曼德好的Clojure /西2017演示文稿在test.check上:https://www.youtube.com/watch?v = r5i_OiZw6Sw –