Skip to content

Latest commit

 

History

History
135 lines (99 loc) · 4.56 KB

approximately-equal.md

File metadata and controls

135 lines (99 loc) · 4.56 KB

=?

This adds a new test expression type =? that uses a Methodical multimethod to decide whether expected and actual should be "approximately equal". It dispatches on the types of expected and actual.

Now while you can already write all the sort of "approximately equal" things you want in theory using schema= (defined in mb.hawk.assert-exprs, in practice it's a bit of a hassle. Want to convert an = to schema= and change one key in a map to use s/Int instead of a specific number? Have fun wrapping every other value in s/eq. Want to ignore unused keys like partial=? You need to stick s/Keyword s/Any in every. single. map. =? takes the best of schema= and partial=, steals a few ideas from Expectations, and is more powerful and easier to use than any of those three.

= usages can be replaced with =? with no other changes -- you can replace that one single key with a predicate function and leave everything else the same.

Here's some rules I've defined already:

  • Two regex patterns that are the exact same pattern should be considered =?. (For some wacko reason regex patterns aren't equal unless they're the same object)

  • An expected plain Clojure map should be approximately equal to an actual record type. We shouldn't need some hack like mt/derecordize to be able to write tests for this stuff

  • an expected regex pattern should be approximately equal to an actual string if the string matches the regex. (This is what re= currently does. We can replace re= with =? entirely.)

  • an expected function should be approximately equal to a an actual value if (expected actual) returns truthy.

  • an expected map should be approximately equal to an actual map if all the keys in expected are present in actual and their respective values are approximately equal. In other words, extra keys in actual should be ignored (this is what our partial= works)

  • Motivating example: two sublcasses of Temporal e.g. OffsetDateTime and ZonedDateTime should be =? if we would print them exactly the same way.

Defining new =? behaviors is as simple as writing a new defmethod.

(methodical/defmethod =?-diff [java.util.regex.Pattern String]
  [expected-regex s]
  (when-not (re-matches expected-regex s)
    (list 'not (list 're-matches expected-regex s))))

Methods are expected to return nil if things are approximately equal, or a form explaining why they aren't if they aren't. In this case, it returns something like

(not (re-matches #"\d+cans" "toucans")))

This is printed in the correct place by humanized test output and other things that can print diffs.

Built-in functions for other =? behaviors:

Built-in functions for =? are defined in the mb.hawk.assert-exprs.approximately-equal namespace. You can create an alias for this namespace like so:

(require '[mb.hawk.assert-exprs.approximately-equal :as =?])

exactly

exactly means results have to be exactly equal as if by =. Use this to get around the normal way =? would compare things. This works inside collections as well.

(is (=? {:m (=?/exactly {:a 1})}
        {:m {:a 1, :b 2}}))
;; =>
expected: {:m (exactly {:a 1})}

  actual: {:m {:a 1, :b 2}}
    diff: - {:m (not (= (exactly {:a 1}) {:a 1, :b 2}))}
          + nil

schema

schema compares things to a schema.core Schema:

(is (=? {:a 1, :b (=?/schema {s/Keyword s/Int})}
        {:a 1, :b {:c 2}}))
=> ok

(is (=? {:a 1, :b (=?/schema {s/Keyword s/Int})}
        {:a 1, :b {:c 2.0}}))
=>
expected: {:a 1, :b (schema {(pred keyword?) (pred integer?)})}

  actual: {:a 1, :b {:c 2.0}}
    diff: - {:b {:c (not (integer? 2.0))}}
          + nil

malli

malli compares things to a malli schema:

(is (=? {:a 1, :b (=?/malli [:map-of :keyword :int])}
        {:a 1, :b {:c 2}}))
=> ok

(is (=? {:a 1, :b (=?/malli [:map-of :keyword :int])}
        {:a 1, :b {:c 2.0}}))
=>
expected: {:a 1, :b (malli [:map-of :keyword :int])}
  actual: {:a 1, :b {:c 2.0}}
    diff: - {:b {:c ["should be an integer"]}}

approx

approx compares whether two numbers are approximately equal:

;; is the difference between actual and 1.5 less than ±0.1?
(is (=? (=?/approx [1.5 0.1])
        1.51))
=> true

(is (=? (=?/approx [1.5 0.1])
        1.6))
=>
expected: (approx [1.5 0.1])

  actual: 1.6
    diff: - (not (approx 1.5 1.6 #_epsilon 0.1))
          + nil