【问题标题】:Is it common for people to test their clojure.spec specs?人们测试他们的 clojure.spec 规范是否常见?
【发布时间】:2021-07-03 12:18:54
【问题描述】:

我正在一个人学习 Clojure,我一直在做一个简单的玩具项目来创建一个 Kakebo(日本预算工具)供我学习。首先我将使用 CLI,然后是 API。

自从我刚刚开始,我就能够“了解”规范,这似乎是 clojure 中用于验证的一个很好的工具。所以,我的问题是:

  1. 人们测试自己的书面规范?
  2. 我用下面的代码测试了我的。有什么建议可以让这变得更好?

据我了解,有一些方法可以通过生成测试自动测试功能,但对于基本规范,这种测试是一种好的做法吗?

规格文件:

(ns kakebo.specs
  (:require [clojure.spec.alpha :as s]))


(s/def ::entry-type #{:income :expense})
(s/def ::expense-type #{:fixed :basic :leisure :culture :extras})
(s/def ::income-type #{:salary :investment :reimbursement})
(s/def ::category-type (s/or ::expense-type ::income-type))
(s/def ::money (s/and double? #(> % 0.0)))
(s/def ::date (java.util.Date.))
(s/def ::item string?)
(s/def ::vendor (s/nilable string?))
(s/def ::entry (s/keys :req [::entry-type ::date ::item ::category-type ::vendor ::money]))

测试文件:

(ns kakebo.specs-test
  (:require [midje.sweet :refer :all]
            [clojure.spec.alpha :as s]
            [kakebo.specs :refer :all]))

(facts "money"
       (fact "bigger than zero"
             (s/valid? :kakebo.specs/money 100.0) => true
             (s/valid? :kakebo.specs/money -10.0) => false)
       (fact "must be double"
             (s/valid? :kakebo.specs/money "foo") => false
             (s/valid? :kakebo.specs/money 1) => false))

(facts "entry types"
       (fact "valid types"
             (s/valid? :kakebo.specs/entry-type :income) => true
             (s/valid? :kakebo.specs/entry-type :expense) => true
             (s/valid? :kakebo.specs/entry-type :fixed) => false))

(facts "expense types"
       (fact "valid types"
             (s/valid? :kakebo.specs/expense-type :fixed) => true))

最后一个问题,如果我尝试以下导入,为什么我无法访问规范:

(ns specs-test
  (:require [kakebo.specs :as ks]))

(fact "my-fact" (s/valid? :ks/money 100.0) => true)

【问题讨论】:

  • ::ks/money 应该可以工作(注意两个前导 :
  • @cfrick 是因为来自另一个命名空间的符号现在与当前命名空间“混合”,因此我可以使用两个 ::? 访问它
  • 这些文档适用于 cljs,但它们解释得很好:cljs.github.io/api/syntax/keyword-qualify

标签: unit-testing clojure clojure.spec midje clojure-testing


【解决方案1】:

无论我是否使用规范,我个人都不会编写与代码紧密耦合的测试。这几乎是对每一行代码的测试——这可能很难维护。

规格中有几个看起来是错误的:

;; this will not work, you probably meant to say the category type 
;; is the union of the expense and income types
(s/def ::category-type (s/or ::expense-type ::income-type))

;; this will not work, you probably meant to check if that the value 
;; is an instance of the Date class
(s/def ::date (java.util.Date.))

通过将您拥有的原子规范组合成更高级别的规范,您可以真正从规范中获得很多东西,这些规范在您的应用程序中完成繁重的工作。我会测试这些更高级别的规范,但通常它们可能落后于常规功能,并且这些规范可能根本不会暴露。

例如,您将entry 定义为其他规范的组合:

(s/def ::entry (s/keys :req [::entry-type ::date ::item ::category-type ::vendor ::money]))

这适用于验证所有必需的数据是否存在并用于生成使用此数据的测试,但数据中有一些传递依赖关系,例如 :expense 不能属于 :salary 类型,因此我们可以将其添加到entry规范:

;; decomplecting the entry types
(def income-entry? #{:income})
(def expense-entry? #{:expense})
(s/def ::entry-type (clojure.set/union expense-entry? income-entry?))

;; decomplecting the category types
(def expense-type? #{:fixed :basic :leisure :culture :extras})
(def income-type? #{:salary :investment :reimbursement})
(s/def ::category-type (clojure.set/union expense-type? income-type?))

(s/def ::money (s/and double? #(> % 0.0)))
(s/def ::date (partial instance? java.util.Date))
(s/def ::item string?)
(s/def ::vendor (s/nilable string?))

(s/def ::expense
  (s/cat ::entry-type expense-entry?
         ::category-type expense-type?))

(s/def ::income
  (s/cat ::entry-type income-entry?
         ::category-type income-type?))

(defn expense-or-income? [m]
  (let [data (map m [::entry-type ::category-type])]
    (or (s/valid? ::expense data)
        (s/valid? ::income data))))

(s/def ::entry
  (s/and
   expense-or-income?
   (s/keys :req [::entry-type ::date ::item
                 ::category-type ::vendor ::money])))

根据应用程序甚至上下文,您可能有不同的规范来描述相同的数据。上面我将expenseincome 组合成entry,这可能有利于输出到报告或电子表格,但在应用程序的另一个区域,您可能希望将它们完全分开以进行数据验证;这确实是我最常使用规范的地方 - 在系统边界,例如用户输入、数据库调用等。

我对规范进行的大多数测试都在验证进入应用程序的数据方面。我测试单个规范的唯一时间是它们是否包含业务逻辑,而不仅仅是数据类型信息。

【讨论】:

  • 感谢您的回答!我认为这现在更清楚了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
  • 1970-01-01
  • 2016-10-22
  • 2020-05-03
  • 2021-02-03
  • 2011-01-26
  • 1970-01-01
相关资源
最近更新 更多