Clojure.Spec验证中有意义的错误消息:pre

Clojure.Spec验证中有意义的错误消息:pre,clojure,clojure.spec,Clojure,Clojure.spec,我用最后几天的时间深入研究了clojure和ClojureScript中的clojure.spec 到目前为止,我发现在依赖于特定格式数据的公共函数中,在:pre和:post中使用规范作为保护最有用 (defn person-name [person] {:pre [(s/valid? ::person person)] :post [(s/valid? string? %)]} (str (::first-name person) " " (::last-name person)

我用最后几天的时间深入研究了clojure和ClojureScript中的clojure.spec

到目前为止,我发现在依赖于特定格式数据的公共函数中,在
:pre
:post
中使用规范作为保护最有用

(defn person-name [person]
  {:pre [(s/valid? ::person person)]
   :post [(s/valid? string? %)]}
  (str (::first-name person) " " (::last-name person)))
这种方法的问题是,我得到了一个
java.lang.AssertionError:Assert失败:(s/valid?::person)
,没有任何关于什么不符合规范的信息

有人知道如何在
:pre
:post
中获得更好的错误消息吗


我知道
conform
explain*
,但这对那些
:pre
:post
防护装置没有帮助。

我想你应该使用
spec/instrument
来验证功能输入和输出,而不是前后条件

在这篇博文的底部有一个很好的例子:。快速总结:您可以使用:args和:ret键(从而替换前置和后置条件)为函数定义规范,包括输入值和返回值,并使用
spec/fdef
,对其进行检测,当其不符合规范时,您将获得类似于使用
explain
的输出

从该链接派生的最小示例:

(spec/fdef your-func
    :args even?
    :ret  string?)


(spec/instrument #'your-func)
这相当于设置一个前提条件,即函数有一个整型参数和一个后置条件,即它返回一个字符串。除非你得到更多有用的错误,就像你正在寻找的一样


官方指南中的更多详细信息:----请参见标题“规范函数”下的内容。

如果不考虑是否应使用前置条件和后置条件来验证函数参数,可以通过使用
clojure.test/is
包装谓词,从前置条件和后置条件打印更清晰的消息,如以下答案所示:

因此,您的代码可以如下所示:

(ns pre-post-messages.core
  (:require [clojure.spec :as s]
            [clojure.test :as t]))

(defn person-name [person]
  {:pre [(t/is (s/valid? ::person person))]
   :post [(t/is (s/valid? string? %))]}
  (str (::first-name person) " " (::last-name person)))

(def try-1
  {:first-name "Anna Vissi"})

(def try-2
  {::first-name "Anna"
   ::last-name "Vissi"
   ::email "Anna@Vissi.com"})

(s/def ::person (s/keys :req [::first-name ::last-name ::email]))
评估

pre-post-messages.core> (person-name  try-2)
会产生

"Anna Vissi"
FAIL in () (core.clj:6)

expected: (s/valid? :pre-post-messages.core/person person)

  actual: (not (s/valid? :pre-post-messages.core/person {:first-name "Anna Vissi"}))

AssertionError Assert failed: (t/is (s/valid? :pre-post-messages.core/person person))  pre-post-messages.core/person-name (core.clj:5)
评价

pre-post-messages.core> (person-name  try-1)
会产生

"Anna Vissi"
FAIL in () (core.clj:6)

expected: (s/valid? :pre-post-messages.core/person person)

  actual: (not (s/valid? :pre-post-messages.core/person {:first-name "Anna Vissi"}))

AssertionError Assert failed: (t/is (s/valid? :pre-post-messages.core/person person))  pre-post-messages.core/person-name (core.clj:5)

在较新的alphas中,现在有
s/assert
,可用于断言输入或返回值与规范匹配。如果有效,则返回原始值。如果无效,则抛出一个断言错误和解释结果。断言可以打开或关闭,甚至可以选择完全从编译代码中省略,从而不影响生产

(s/def ::first-name string?)
(s/def ::last-name string?)
(s/def ::person (s/keys :req [::first-name ::last-name]))
(defn person-name [person]
  (s/assert ::person person)
  (s/assert string? (str (::first-name person) " " (::last-name person))))

(s/check-asserts true)

(person-name 10)
=> CompilerException clojure.lang.ExceptionInfo: Spec assertion failed
val: 10 fails predicate: map?
:clojure.spec/failure  :assertion-failed
 #:clojure.spec{:problems [{:path [], :pred map?, :val 10, :via [], :in []}], :failure :assertion-failed}

当您不想使用
s/assert
,或无法启用
s/check assserts
时,此功能非常有用。改进米索科利的答案:

:pre
只关心返回的值都是真实的,因此我们可以将返回值
“Success!\n”
转换为
true
(为严格起见),并且
在输出不成功的情况下抛出带有解释和输入数据的错误

(defn validate [spec input]
    (let [explanation (s/explain-str spec input)]
        (if (= explanation "Success!\n") 
            true
            (throw (ex-info explanation {:input input}))))
这个的一个变体可能是这个,但它将运行规范两次:

(defn validate [spec input]
    (if (s/valid? spec input) 
        true
        (throw (ex-info (s/explain spec input) {:input input}))))
用法:

(defn person-name [person]
  {:pre [(validate ::person person)]}
  (str (::first-name person) " " (::last-name person)))

听起来很合理。将尝试此操作。但这不会检查返回值,因此仪器仅替换:pre。其思想是使用test.check来测试函数是否正确地将输入映射到输出。然后,通过常规测试,在集成中对功能进行仪器化和测试。最后,您可以在任何需要生产保护的地方使用s/assert,如Alex Miller的回答中所述。ret值也可以使用chestration进行检测。我强烈推荐使用@xtreak所说的管弦乐队。如果作为工作流的一部分运行
repl/refesh
,请不要忘记每次重新插入所有FN。与orchestra配对可以快速、准确地解决故障。我不确定是否需要在生产代码中使用test
clojure.test
名称空间。但这似乎是一个可行的选择,从
:pre
卫兵那里获得有意义的信息。@Diegorfrings生产代码中
clojure.test/is
表达式的问题似乎有点特殊:实际运行测试(通过
lein test
)会弄乱计数器。
deftest
表达式中的
is
表达式不仅计入测试通过/失败总数,而且生产代码调用的任何
is
也计入。否则,可以考虑<代码> Culjule.Test/Is<代码>作为返回布尔函数的另一个函数,生成通过可重写<代码> Culjule.Test/Reals< /Cudio>编写的输出。毕竟,人们也可以在生产Java代码中使用例如Hamcrest matchers,而不仅仅是在JUnit类中。如果能够在pre:and:post“hooks”中使用s/assert或s/explain*也会很好……您可以在pre和post中使用它们?当我在
(defn f[x]{:pre[(s/assert string?x)]}(println x)中使用这个(失败的)s/assert时,一切都不会发生
但s/explain确实捕获了它,这样对我来说很好。(1.9.0-alpha13)您是否启用了断言?您需要调用或设置clojure.spec.check-asserts系统属性来启用它们。您是对的。(s/assert)在启用断言的情况下工作。我的问题源于我把
(s/assert-string?x)
(assert-string?x)
(来自clojure.core)。后者需要编写
(assert(string?x))
才能生效。