Clojure.Spec验证中有意义的错误消息:pre
我用最后几天的时间深入研究了clojure和ClojureScript中的clojure.spec 到目前为止,我发现在依赖于特定格式数据的公共函数中,在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)
: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配对可以快速、准确地解决故障。我不确定是否需要在生产代码中使用testclojure.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))
才能生效。