Часто ли люди тестируют свои спецификации clojure.spec?
Я изучаю Clojure, сам по себе, и я работал над простым игрушечным проектом по созданию Kakebo (японский инструмент для составления бюджета), который я мог бы изучить. Сначала я буду работать над CLI, затем над API.
С тех пор, как я только начал, я смог "разобраться" со спецификациями, что кажется отличным инструментом в Clojure для проверки. Итак, мои вопросы:
- Люди тестируют свои собственные письменные спецификации?
- Я тестировал свой как следующий код. Совет, как это исправить?
Насколько я понимаю, есть способы автоматического тестирования функций с помощью генеративного тестирования, но для базовых спецификаций, является ли такой тест хорошей практикой?
Файл спецификаций:
(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)
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.))
Вы действительно можете многое выйти из спецификации, составив атомарные спецификации, которые у вас есть, в спецификации более высокого уровня, которые делают тяжелую работу в вашем приложении. Я бы протестировал эти спецификации более высокого уровня, но часто они могут отставать от обычных функций, и спецификации могут вообще не отображаться.
Например, вы определили как набор других спецификаций:
(s/def ::entry (s/keys :req [::entry-type ::date ::item ::category-type ::vendor ::money]))
Это работает для проверки наличия всех необходимых данных и для создания тестов, использующих эти данные, но внутри данных есть некоторые транзитивные зависимости, такие как
;; 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])))
В зависимости от приложения или даже контекста у вас могут быть разные спецификации, описывающие одни и те же данные. Выше я объединил
Большинство тестов, которые у меня есть для спецификаций, относятся к проверке данных, поступающих в приложение. Единственный раз, когда я тестирую отдельные спецификации, это если в них есть бизнес-логика, а не только информация о типе данных.