Tuesday, November 01, 2011

Clojure: expectations and Double/NaN

Clojure Unit Testing with Expectations Part One
Clojure Unit Testing with Expectations Part Two
Clojure Unit Testing with Expectations Part Three
Clojure Unit Testing with Expectations Part Four (this entry)
Clojure Unit Testing with Expectations Part Five
Clojure Unit Testing with Expectations Part Six

I'm not really a fan of Double/NaN in general, but sometimes it seems like the least evil choice. When I find myself in one of those cases I always hate having to write tests in a way that differs from all the other tests in the codebase. A goal I've always had with expectations is to keep the syntax consistent, and as a result I've chosen to treat Double/NaN as equal to Double/NaN when in the various Clojure data structures.

The following examples demonstrate Double/NaN being treated as equal
`;; allow Double/NaN equality in a map(expect {:a Double/NaN :b {:c Double/NaN}} {:a Double/NaN :b {:c Double/NaN}});; allow Double/NaN equality in a set(expect #{1 Double/NaN} #{1 Double/NaN});; allow Double/NaN equality in a list(expect [1 Double/NaN] [1 Double/NaN])jfields\$ lein expectationsRan 3 tests containing 3 assertions in 32 msecs0 failures, 0 errors.`
As you would expect, you can also count on Double/NaN being considered equal even if you are using the 'in' function.
`;; allow Double/NaN equality when verifying values are in a map(expect {:a Double/NaN :b {:c Double/NaN}} (in {:a Double/NaN :b {:c Double/NaN} :d "other stuff"}));; allow Double/NaN equality when verifying it is in a set(expect Double/NaN (in #{1 Double/NaN}));; allow Double/NaN equality when verifying it's existence in a list(expect Double/NaN (in [1 Double/NaN]))jfields\$ lein expectationsRan 3 tests containing 3 assertions in 32 msecs0 failures, 0 errors.`
For completeness I'll also show the examples of each of these examples failing.
`;; allow Double/NaN equality in a map(expect {:a Double/NaN :b {:c Double/NaN}} {:a nil :b {:c Double/NaN}});; allow Double/NaN equality with in fn and map(expect {:a Double/NaN :b {:c nil}} (in {:a Double/NaN :b {:c Double/NaN} :d "other stuff"}));; allow Double/NaN equality in a set(expect #{1 Double/NaN} #{1 nil});; allow Double/NaN equality with in fn and set(expect Double/NaN (in #{1 nil}));; allow Double/NaN equality in a list(expect [1 Double/NaN] [1 nil]);; allow Double/NaN equality with in fn and list(expect Double/NaN (in [1 nil]))jfields\$ lein expectationsfailure in (core.clj:5) : sample.test.core           (expect {:a Double/NaN, :b {:c Double/NaN}}                   {:a nil, :b {:c Double/NaN}})           expected: {:a NaN, :b {:c NaN}}                 was: {:a nil, :b {:c NaN}}           :a expected: NaN                   was: nilfailure in (core.clj:8) : sample.test.core           (expect {:a Double/NaN, :b {:c nil}} (in {:a Double/NaN, :b {:c Double/NaN}, :d "other stuff"}))           expected: {:a NaN, :b {:c nil}}                  in: {:a NaN, :b {:c NaN}, :d "other stuff"}           :b {:c expected: nil                       was: NaNfailure in (core.clj:11) : sample.test.core           (expect #{1 Double/NaN} #{nil 1})           expected: #{NaN 1}                 was: #{nil 1}           nil are in actual, but not in expected           NaN are in expected, but not in actualfailure in (core.clj:14) : sample.test.core           (expect Double/NaN (in #{nil 1}))           key NaN not found in #{nil 1}failure in (core.clj:17) : sample.test.core           (expect [1 Double/NaN] [1 nil])           expected: [1 NaN]                 was: [1 nil]           nil are in actual, but not in expected           NaN are in expected, but not in actualfailure in (core.clj:20) : sample.test.core           (expect Double/NaN (in [1 nil]))           value NaN not found in [1 nil]Ran 6 tests containing 6 assertions in 66 msecs6 failures, 0 errors.`
There always seems to be downsides to using NaN, so I tend to look for the least painful path. Hopefully expectations provides the most pain-free path when your tests end up needing to include NaN.