It is implementation of Hamcrest idea in Common Lisp.
It simplifes unittests and make them more readable. Hamcrest uses idea of pattern-matching, to construct matchers from different pieces and to apply them to the data.
Why not pattern-matching library?
You may ask: "Why dont use a pattern-matching library, like Optima?"
Here is another example from another library
log4cl-json, where I want
to check that some fields in plist have special values and other key is not
present. Here is the data:
(defvar log-item '(:|@message| "Some" :|@timestamp| 122434342 ;; this field is wrong and ;; shouldn't be here :|@fields| nil))
With Optima I could write this code to match the data:
(ok (ematch log-item ((and (guard (property :|@message| m) (equal m "Some")) (property :|@timestamp| _) (not (property :|@fields| _))) t)) "Log entry has message, timestamp, but not fields")
But error message will be quite cumbersome:
× Aborted due to an error in subtest "Simple match" Raised an error Can't match ((:|@fields| NIL :|@timestamp| "2017-01-03T16:42:00.991444Z" :|@message| "Some")) with ((COMMON-LISP:AND (GUARD (PROPERTY :|@message| M) (EQUAL M "Some")) (PROPERTY :|@timestamp| _) (NOT (PROPERTY :|@fields| _)))). (expected: :NON-ERROR)
CL-HAMCREST is more concise and clear
cl-hamcrest test becomes more readable:
(assert-that log-item (has-plist-entries :|@message| "Some" :|@timestamp| _) (hasnt-plist-keys :|@fields|))
As well, as output about the failure:
× Key :|@fields| is present in object, but shouldn't
That is because
cl-hamcrest tracks the context and works
together with testing framework, to output all information
to let you understand where the problem is.
Why not just use Prove's assertions?
To draw a full picture, here is test, written in plain Prove's assertions:
(ok (member :|@message| log-item)) (is (getf log-item :|@message|) "Some") (ok (member :|@timestamp| log-item)) (ok (not (member :|@fields| log-item)))
And it's output:
✓ (:|@message| "Some") is expected to be T ✓ "Some" is expected to be "Some" ✓ (:|@timestamp| "2017-01-03T16:57:17.988810Z" :|@message| "Some") is expected to be T × NIL is expected to be T
is not as clear, if you'll try to figure out
NIL is expected to be T mean.
Description of all supported matchers, you can find in the documentation.
- Logical matchers:
any-of– Matches if any of the given matchers evaluate to True.
is-not– Inverts the given matcher to its logical negation (think if we need it, and how to show the results, here are results how it works in PyHamcrest – it just sees that matcher returned True and raises Assertion error with full object's content and matcher's description with prepended 'not' particle).
- Object matchers:
hasnt-some-keysmatchers, corresponding to
has-alist-entrieswork with keys other than keyword right now it uses eql to compare keys.
- Sequence matchers:
is-in– Matches if evaluated object is present in a given sequence.
has-items– Matches if all of the given matchers are satisfied by any elements of the sequence.
only-contains– Matches if each element of sequence satisfies any of the given matchers.
- Other features:
- Use uniq CommonLisp feature to restart signaled conditions to collect all problems with data when there are few problems with keys.
sphinx sphinxcontrib-cldomain (https://github.com/russell/sphinxcontrib-cldomain) pygments-cl-repl sphinx-bootstrap-theme
cd docs && make html