问题
(defn string-to-string [s1]
{:pre [(string? s1)]
:post [(string? %)]}
s1)
I like :pre and :post conditions, they allow me to figure out when I have put "square pegs in round holes" more quickly. Perhaps it is wrong, but I like using them as a sort of poor mans type checker. This isn't philosophy though, this is a simple question.
It seems in the above code that I should easily be able to determine that s1
is a function argument in the :pre
condition. Similarily, %
in the :post
condition is always the function return value.
What I would like is to print the value of s1
or %
when either of these respective conditions fail within the AssertionError. So I get something like
(string-to-string 23)
AssertionError Assert failed: (string? s1)
(pr-str s1) => 23
With the AssertionError containing a single line for every variable that was identified as being from the function argument list and that was referenced in the failing test. I would also like something similar when the return value of the function fails the :post
condition.
This would make it trivial to quickly spot how I misused a function when trying to diagnose from the AssertionError. It would at least let me know if the value is nil
or an actual value (which is the most common error I make).
I have some ideas that this could be done with a macro, but I was wondering if there was any safe and global way to basically just redefine what (defn
and (fn
and friends do so that :pre
and :post
would also print the value(s) that lead to the test failing.
回答1:
You could wrap your predicate with the is macro from clojure.test
(defn string-to-string [s1]
{:pre [(is (string? s1))]
:post [(is (string? %))]}
s1)
Then you get:
(string-to-string 10)
;FAIL in clojure.lang.PersistentList$EmptyList@1 (scratch.clj:5)
;expected: (string? s1)
;actual: (not (string? 10))
回答2:
@octopusgrabbus kind of hinted at this by proposing (try ... (catch ...))
, and you mentioned that that might be too noisy, and is still wrapped in an assert. A simpler and less noisy variant of this would be a simple (or (condition-here) (throw-exception-with-custom-message))
syntax, like this:
(defn string-to-string [s1]
{:pre [(or (string? s1)
(throw (Exception. (format "Pre-condition failed; %s is not a string." s1))))]
:post [(or (string? %)
(throw (Exception. (format "Post-condition failed; %s is not a string." %))))]}
s1)
This essentially lets you use pre- and post-conditions with custom error messages -- the pre- and post-conditions are still checked like they normally would be, but your custom exception is evaluated (and thus thrown) before the AssertionError can happen.
回答3:
Something like below where clojure spec is explaining the problem? This will throw an assertion error which you can catch.
(defn string-to-string [s1]
{:pre [ (or (s/valid? ::ur-spec-or-predicate s1)
(s/explain ::ur-spec-or-predicate s1)]}
s1)
来源:https://stackoverflow.com/questions/24834116/how-can-i-get-clojure-pre-post-to-report-their-failing-value