cl-hamcrest

2017-06-30

cl-hamcrest

image

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

With 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 what does NIL is expected to be T mean.

Description of all supported matchers, you can find in the documentation.

Roadmap

  • Logical matchers:
    • all-of ? rename has-all.
    • 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:
    • Add hasnt-some-keys matchers, corresponding to has-some-entries.
    • Make has-alist-entries work 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.

Building Documentation

Requirements

Python packages

sphinx sphinxcontrib-cldomain (https://github.com/russell/sphinxcontrib-cldomain) pygments-cl-repl sphinx-bootstrap-theme

Lisp

cl-launch (http://www.cliki.net/CL-Launch)

To build

cd docs && make html

Author
Alexander Artemenko
License
New BSD License