2016-06-17 3 views
14

Я использовал последние дни, чтобы копать глубже в clojure.spec в Clojure и ClojureScript.Сознательное сообщение об ошибке для Clojure.Spec проверки в: pre

До сих пор я нахожу его наиболее полезным, чтобы использовать спецификации в качестве охранников в :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 failed: (s/valid? ::person person) без какой-либо информации о том, что именно не встретил спецификацию.

кто-нибудь идею , как получить лучшее сообщение об ошибке в :pre или :post охранников?

Я знаю о conform и explain*, но это не помогает в этих :pre или :post охранников.

ответ

5

Я думаю, что идея заключается в том, что вы используете spec/instrument для проверки ввода и вывода функций, а не условий предварительной и пост.

Есть хороший пример в нижней части этого сообщения в блоге: http://gigasquidsoftware.com/blog/2016/05/29/one-fish-spec-fish/. Краткое описание: вы можете определить спецификацию для функции, включая как входные, так и возвращаемые значения, используя ключи: args и: ret (таким образом, заменяя оба условия pre и post), с помощью spec/fdef, обработайте его, и вы получите результат, аналогичный использованию explain когда он не отвечает спецификации.

Minimal пример, полученный из этой ссылки:

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


(spec/instrument #'your-func) 

И это эквивалентно положить предпосылку, что функция имеет целочисленный аргумент и постусловие, что он возвращает строку. Кроме того, вы получаете гораздо более полезные ошибки, точно так же, как вы ищите.

Подробнее в официальном руководстве: https://clojure.org/guides/spec --- см. Под заголовком «Функции функции».

+1

Звучит разумно. Попробуй это. – DiegoFrings

+3

Это не проверяет возвращаемые значения, поэтому инструмент заменяет только: pre. Идея заключается в том, что вы используете test.check, чтобы проверить правильность ввода данных в вашу функцию. Затем вы проводите тестирование и тестирование своих функций в процессе интеграции с помощью обычных тестов. Наконец, вы используете s/assert везде, где вы хотите, чтобы производственные охранники, например, упомянуты в ответе Алекса Миллера. –

+0

: значения ret также могут быть использованы с использованием оркестра. https://github.com/jeaye/orchestra – xtreak

1

Не принимая во внимание, если вы должны использовать заранее и пост условия для проверки аргументов функции, существует способ печати несколько четких сообщений от до и после условий, окружив свой предикат с clojure.test/is, как это было предложено в ответ ниже:

How can I get Clojure :pre & :post to report their failing value?

Итак ваш код может выглядеть следующим образом:

(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 "[email protected]"}) 

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

Оценка

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

будет производить

"Anna Vissi" 

и оценка

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

будет производить

+0

Я не уверен насчет необходимости использовать пространство имен 'clojure.test' в производственном коде. Но это кажется жизнеспособным вариантом, чтобы получить содержательное сообщение от ': pre' охранников. – DiegoFrings

7

В новых альф, есть теперь s/assert, которые могут быть использованы утверждать, что входной или возвращаемое значение соответствует спецификации. Если оно действительное, возвращается исходное значение. Если это неверно, с результатом объяснения возникает ошибка утверждения. Утверждения могут быть включены или выключены и могут быть необязательно исключены из скомпилированного кода целиком, чтобы иметь эффект 0 производства.

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

Было бы неплохо иметь возможность использовать s/assert или s/explain * в pre: и: post "hooks" тоже ... –

+0

Вы можете использовать их в pre и post? –

+0

Ничего не происходит, когда у меня есть это (неудачное) s/assert в '(defn f [x] {: pre [(s/assert string? X)]} (println x))' Но s/explain действительно фиксирует его так, чтобы хорошо для меня. (1.9.0-alpha13) –

 Смежные вопросы

  • Нет связанных вопросов^_^