views:

193

answers:

2

I have a small unit test macro

  (defmacro is [expr value]
        `(if (= ~expr ~value)
             'yes
             (println "Expected " ~value "In" ~expr "But Evaluated to" ~expr)))

How do I correctly write the error message? Right now it gives

Clojure 1.0.0-
1:1 user=> (is (+ 2 2) 4)
user/yes
1:2 user=> (is (+ 2 2) 5)
Expected  5 In 4 But Evaluated to 4

I want to evaluate the expression and say 'yes if the evaluated value matches expected else print the error with the unevaluated expression instead of the value after "In".

+9  A: 

Original Answer

I think this is what you are looking for:

(defmacro is [expr value]
    `(if (= ~expr ~value)
         true
         (println "Expected " ~value "In" (str (quote ~expr)) 
                  "But Evaluated to" ~expr)))

what (quote ~expr) does is bring the s-expression represented by expr into your "template", but prevents it from being evaluated, then applying str to the unevaluated s-expression makes it into a string for concatenation with the other strings of the error message. So, :

user=> (is (+ 2 2) 5)
Expected  5 In (+ 2 2) But Evaluated to 4

produces the desired behavior.

Rather than using 'yes to report success, you can simply use true as the report for the success of the test, giving:

user=> (is (+ 2 2) 4)
true

and avoiding the problem of 'yes being returned as a qualified symbol. If you need a symbol for some reason, then you could make a keyword:

(defmacro is [expr value]
    `(if (= ~expr ~value)
         :yes
         (println "Expected " ~value "In" (str (quote ~expr)) 
                  "But Evaluated to" ~expr)))


user=> (is (+ 2 2) 4)
:yes



Answer to Question Asked in Comments

You asked in comments:

I am sorry I should have been clearer in my question. What would be the difference between a symbol and keyword in this case?

Consider your original definition (with the correction to make the error return the way you wanted):

(defmacro is [expr value]
`(if (= ~expr ~value)
     'yes
     (println "Expected " ~value "In" (str (quote ~expr)) 
              "But Evaluated to" ~expr)))

I imagine that you want the 'yes thinking that you could use to test against 'yes in other contexts (one often sees these types of tests in introductory lisp texts). But 'yes is used in your macro definition, it returns a qualified symbol when the test is passes:

user=> (is (+ 2 2) 4)
user/yes

Now, this is not what you would want. Imagine that you said this:

user=> (= 'yes (is (+ 2 2) 4))
false

To get a true answer, you would have to say this (using a syntax quote):

user=> (= `yes (is (+ 2 2) 4))
true

If you defined your macro to return :yes, then you get the checkable return without having to syntax quote the object you are using to do the checking:

user=> (= :yes (is (+ 2 2) 4))
true

But this is all superfluous because you are really interested in whether (+ 2 2) returns 4, i.e. whether your assertion is true or false rather than whether 'yes equals `yes or 'yes; you can just have it return true and avoid this checking step (in the real world).

In any case, to understand what qualified symbols are, you need to read the docs on the clojure reader and this SO answer explaining the ins and outs of symbols in clojure.

Pinochle
Thanks. But Why did you make it a keyword? What would be the difference?
kunjaan
The problem with the quoted symbol is that it comes out as a qualified symbol, as you noted. I suggested using 'true' as the best replacement, but threw out the option of using a keyword if, for some reason, you needed something 'symbolike' for reasons having to do the way you've written the rest of the program. I think, though, that 'true' is the way to go in the abstract.
Pinochle
I am sorry I should have been clearer in my question. What would be the difference between a symbol and keyword in this case?
kunjaan
I answered your question in an edit to the original answer.
Pinochle
AWESOME! Thanks a Lot. I wish I could upvote the answer more.
kunjaan
Note that 'value' is evaluated twice. It's better to wrap it with a gensym.
Wojciech Kaczmarek
A: 

You have a crosscutting concern when you have the 'Is' macro print. Have the 'Is' macro return a list that contains something like this (:pass form actual-answer expected-answer) or (:fail form actual-answer expected-answer). You probably have a DefTest macro for aggregating your asserts. That is where you should pp your answer.

Gutzofter