Thursday, September 30, 2010

Clojure: Flatten Keys

I recently needed to take a nested map and flatten the keys. After a bit of trial and error I came up with the following code. (note: the example expects Expectations)
(ns example
(:use expectations))

(defn flatten-keys* [a ks m]
(if (map? m)
(reduce into (map (fn [[k v]] (flatten-keys* a (conj ks k) v)) (seq m)))
(assoc a ks m)))

(defn flatten-keys [m] (flatten-keys* {} [] m))

(expect
{[:z] 1, [:a] 9, [:b :c] Double/NaN, [:b :d] 1, [:b :e] 2, [:b :f :g] 10, [:b :f :i] 22}
(flatten-keys {:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}))

As the test shows, the code converts
{:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
into
{[:z] 1, [:a] 9, [:b :c] Double/NaN, [:b :d] 1, [:b :e] 2, [:b :f :g] 10, [:b :f :i] 22}
Improvement suggestions welcome.

Clojure: Another Testing Framework - Expectations

Once upon a time I wrote Expectations for Ruby. I wanted a simple testing framework that allowed me to specify my test with the least amount of code.

Now that I'm spending the majority of my time in Clojure, I decided to create a version of Expectations for Clojure.

At first it started as a learning project, but I kept adding productivity enhancements. Pretty soon, it became annoying when I wasn't using Expectations. Obviously, if you write your own framework you are going to prefer to use it. However, I think the productivity enhancements might be enough for other people to use it as well.

So why would you want to use it?

Tests run automatically. Clojure hates side effects, yeah, I hear you. But, I hate wasting time and repeating code. As a result, Expectations runs all the tests on JVM shutdown. This allows you to execute a single file to run all the tests in that file, without having to specify anything additional. There's also a hook you can call if you don't want the tests to automatically run. (If you are looking for an example, there's a JUnit runner that disables running tests on shutdown)

What to test is inferred from your "expected" value. An equality test is probably the most common test written. In Expectations, an equality test looks like the following example.
(expect 3 (+ 1 2))
That's simple enough, but what if you want to match a regex against a string? The following example does exactly that, and it uses the same syntax.
(expect #"foo" "afoobar")
Other common tests are verifying an exception is thrown or checking the type of an actual value. The following snippets test those two conditions.
(expect ArithmeticException (/ 12 0))

(expect String "foo")
Testing subsets of the actual value. Sometimes you want an exact match, but there are often times when you only care about a subset of the actual value. For example, you may want to test all the elements of a map except the time and id pairs (presumably because they are dynamic). The following tests show how you can verify that some key/value pairs are in a map, a element is in a set, or an element is in a list.
;; k/v pair in map. matches subset
(expect {:foo 1} (in {:foo 1 :cat 4}))

;; key in set
(expect :foo (in (conj #{:foo :bar} :cat)))

;; val in list
(expect :foo (in (conj [:bar] :foo)))
Double/NaN is annoying. (not= Double/NaN Double/NaN) ;=> true. I get it, conceptually. In practice, I don't want my tests failing because I can't compare two maps that happen to have Double/NaN as the value for a matching key. In fact, 100% of the time I want (= Double/NaN Double/NaN) ;=> true. And, yes, I can rewrite the test and use Double/isNaN. I can. But, I don't want to. Expectations allows me to pretend (= Double/NaN Double/NaN) ;=> true. It might hurt me in the future. I'll let you know. For now, I prefer to write concise tests that behave as "expected".

Try rewriting this and using Double/isNaN (it's not fun)
(expect
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}}
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})
Concise Java Object testing. Inevitably, I seem to end up with a few Java objects. I could write a bunch of different expect statements, but I opted for a syntax that allows me to check everything at once.
(given (java.util.ArrayList.)
(expect
.size 0
.isEmpty true))
Trimmed Stacktraces. I'm sure it's helpful to look through Clojure and Java's classes at times. However, I find the vast majority of the time the problem is in my code. Expectations trims many of the common classes that are from Clojure and Java, leaving much more signal than noise. Below is the stacktrace reported when running the failure examples from the Expectations codebase.
failure in (failure_examples.clj:8) : failure.failure-examples
raw: (expect 1 (one))
act-msg: exception in actual: (one)
threw: class java.lang.ArithmeticException-Divide by zero
failure.failure_examples$two__375 (failure_examples.clj:4)
failure.failure_examples$one__378 (failure_examples.clj:5)
failure.failure_examples$G__381__382$fn__387 (failure_examples.clj:8)
failure.failure_examples$G__381__382 (failure_examples.clj:8)
Every stacktrace line is from my code, where the problem lives.

Descriptive Error Messages. Expectations does it's best to give you all the important information when a failure does occur. The following failure shows what keys are missing from actual and expected as well is which values do not match.
running

(expect
{:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})

generates

failure in (failure_examples.clj:110) : failure.failure-examples
raw: (expect {:z 1, :a 9, :b {:c Double/NaN, :d 1, :e 2, :f {:g 10, :i 22}}} {:x 1, :a Double/NaN, :b {:c Double/NaN, :d 2, :e 4, :f {:g 11, :h 12}}})
result: {:z 1, :a 9, :b {:c NaN, :d 1, :e 2, :f {:g 10, :i 22}}} are not in {:x 1, :a NaN, :b {:c NaN, :d 2, :e 4, :f {:g 11, :h 12}}}
exp-msg: :x is in actual, but not in expected
:b {:f {:h is in actual, but not in expected
act-msg: :z is in expected, but not in actual
:b {:f {:i is in expected, but not in actual
message: :b {:e expected 2 but was 4
:b {:d expected 1 but was 2
:b {:f {:g expected 10 but was 11
:a expected 9 but was NaN
note: I know it's a bit hard to read, but I wanted to cover all the possible errors with one example. In practice you'll get a few messages that will tell you exactly what is wrong.

For example, the error tells you
:b {:f {:g expected 10 but was 11
With that data it's pretty easy to see the problem in
(expect
{:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})
Expectations also tells you, when comparing two lists:
  • if the lists are the same, but differ only in order
  • if the lists are the same, but one list has duplicates
  • if the lists are not the same, which list is larger

    JUnit integration. My project uses both Java and Clojure. I like running my tests in IntelliJ and I like TeamCity running my tests as part of the build. To accomplish this using Expectations all you need to do is create a java class similar to the example below.
    import expectations.junit.ExpectationsTestRunner;
    import org.junit.runner.RunWith;

    @RunWith(expectations.junit.ExpectationsTestRunner.class)
    public class FailureTest implements ExpectationsTestRunner.TestSource{

    public String testPath() {
    return "/path/to/the/root/folder/holding/your/tests";
    }
    }
    The Expectations Test Runner runs your Clojure tests in the same way that the Java tests run, including the green/red status icons and clickable links when things fail.

    Why wouldn't you use Expectations?

    Support. I'm using it to test my production code, but if I find errors I have to go fix them. You'll be in the same situation. I'll be happy to fix any bugs you find, but I might not have the time to get to it as soon as you send me email.

    If you're willing to live on the bleeding edge, feel free to give it a shot.
  • Wednesday, September 01, 2010

    Clojure: Mocking

    An introduction to clojure.test is easy, but it doesn't take long before you feel like you need a mocking framework. As far as I know, you have 3 options.
    1. Take a look at Midje. I haven't gone down this path, but it looks like the most mature option if you're looking for a sophisticated solution.

    2. Go simple. Let's take an example where you want to call a function that computes a value and sends a response to a gateway. Your first implementation looks like the code below. (destructuring explained)
      (defn withdraw [& {:keys [balance withdrawal account-number]}]
      (gateway/process {:balance (- balance withdrawal)
      :withdrawal withdrawal
      :account-number account-number}))
      No, it's not pure. That's not the point. Let's pretend that this impure function is the right design and focus on how we would test it.

      You can change the code a bit and pass in the gateway/process function as an argument. Once you've changed how the code works you can test it by passing identity as the function argument in your tests. The full example is below.
      (ns gateway)

      (defn process [m] (println m))

      (ns controller
      (:use clojure.test))

      (defn withdraw [f & {:keys [balance withdrawal account-number]}]
      (f {:balance (- balance withdrawal)
      :withdrawal withdrawal
      :account-number account-number}))

      (withdraw gateway/process :balance 100 :withdrawal 22 :account-number 4)
      ;; => {:balance 78, :withdrawal 22, :account-number 4}

      (deftest withdraw-test
      (is (= {:balance 78, :withdrawal 22, :account-number 4}
      (withdraw identity :balance 100 :withdrawal 22 :account-number 4))))

      (run-all-tests #"controller")
      If you run the previous example you will see the println output and the clojure.test output, verifying that our code is working as we expected. This simple solution of passing in your side effect function and using identity in your tests can often obviate any need for a mock.

    3. Solution 2 works well, but has the limitations that only one side-effecty function can be passed in and it's result must be used as the return value.

      Let's extend our example and say that we want to log a message if the withdrawal would cause insufficient funds. (Our gateway/process and log/write functions will simply println since this is only an example, but in production code their behavior would differ and both would be required)
      (ns gateway)

      (defn process [m] (println "gateway: " m))

      (ns log)

      (defn write [m] (println "log: " m))

      (ns controller
      (:use clojure.test))

      (defn withdraw [& {:keys [balance withdrawal account-number]}]
      (let [new-balance (- balance withdrawal)]
      (if (> 0 new-balance)
      (log/write "insufficient funds")
      (gateway/process {:balance new-balance
      :withdrawal withdrawal
      :account-number account-number}))))

      (withdraw :balance 100 :withdrawal 22 :account-number 4)
      ;; => gateway: {:balance 78, :withdrawal 22, :account-number 4}

      (withdraw :balance 100 :withdrawal 220 :account-number 4)
      ;; => log: insufficient funds
      Our new withdraw implementation calls two functions that have side effects. We could pass in both functions, but that solution doesn't seem to scale very well as the number of passed functions grows. Also, passing in multiple functions tends to clutter the signature and make it hard to remember what is the valid order for the arguments. Finally, if we need withdraw to always return a map showing the balance and withdrawal amount, there would be no easy solution for verifying the string sent to log/write.

      Given our implementation of withdraw, writing a test that verifies that gateway/process and log/write are called correctly looks like a job for a mock. However, thanks to Clojure's binding function, it's very easy to redefine both of those functions to capture values that can later be tested.

      The following code rebinds both gateway/process and log/write to partial functions that capture whatever is passed to them in an atom that can easily be verified directly in the test.
      (ns gateway)

      (defn process [m] (println "gateway: " m))

      (ns log)

      (defn write [m] (println "log: " m))

      (ns controller
      (:use clojure.test))

      (defn withdraw [& {:keys [balance withdrawal account-number]}]
      (let [new-balance (- balance withdrawal)]
      (if (> 0 new-balance)
      (log/write "insufficient funds")
      (gateway/process {:balance new-balance
      :withdrawal withdrawal
      :account-number account-number}))))

      (deftest withdraw-test1
      (let [result (atom nil)]
      (binding [gateway/process (partial reset! result)]
      (withdraw :balance 100 :withdrawal 22 :account-number 4)
      (is (= {:balance 78, :withdrawal 22, :account-number 4} @result)))))

      (deftest withdraw-test2
      (let [result (atom nil)]
      (binding [log/write (partial reset! result)]
      (withdraw :balance 100 :withdrawal 220 :account-number 4)
      (is (= "insufficient funds" @result)))))

      (run-all-tests #"controller")
    In general I use option 2 when I can get away with it, and option 3 where necessary. Option 3 adds enough additional code that I'd probably look into Midje quickly if I found myself writing a more than a few tests that way. However, I generally go out of my way to design pure functions, and I don't find myself needing either of these techniques very often.