eg
eg delivers clojure.test
function tests with conciseness.
(deftest inc-test
(is (= 1 (inc 0))))
in eg becomes:
(eg inc 0 1)
Check the ideas driving eg.
Installation
Disclaimer: eg is work-in-progress. Use it at your own risk!
Leiningen/Boot
[eg "0.4.3-alpha"]
Clojure CLI/deps.edn
eg {:mvn/version "0.4.3-alpha"}
Usage
eg
stands for e.g. (short for example), and ge
is just eg
reversed. Reversed example: (ge inc 1 [0])
.
Let's try eg! Start by creating a REPL session and then requiring eg
and ge
:
(require '[eg :refer [eg ge]])
Each eg test tests one function using examples. You could think of it as a function's test definition:
(eg not ; testing clojure.core/not
false ; with input parameter `false`
true) ; returning expected value `true`
a clojure.test
test named not-test
was generated.
If a function under test takes multiple parameters, we need to wrap these under a vector:
(eg * [3 2] 6)
Each eg test can contain an arbitrary number of examples:
(eg *
[3] 3
[3 2] 6)
There are times when we prefer to have expected values
on the left, and input parameters on the right.
For that we use ge
, a useful mnemonic for the inverted flow of the test example:
(ge + 10 [3 7])
Predicates can also be used in place of an expected value:
(eg dec [4] integer?)
=>
or <=
delimiters between input parameters and expected value can be used to improve readability, or
override the default order of eg
or ge
.
(eg hash-map
[:d 1] {:d 1}
[:a 1 :b 2 :c 3 :d 4] => {:a 1 :b 2 :c 3 :d 4}
map? <= {:a 1 :b 2 :c 3 :d 4})
ex
makes it possible to test the result of calling an arbitrary form expression. Typical scenarios include testing the result of calling a macro (eg
, and ge
only support function testing), or decomposing the assertion of different properties or values from calling a form:
(let [test-eg-ret (ex (inc 0) 1)
f-len (count "eg-test-")]
; arrows are compulsory
(ex var? <= test-eg-ret
(-> test-eg-ret meta :test) => boolean
(-> test-eg-ret meta :test) => fn?
(-> test-eg-ret meta :name name (subs f-len)) => not-empty))
;=> eg-test-<rand-id>
(ex (true? false) => false) ;=> eg-test-<rand-id>
There are times when we just want to test a certain input parameter value, but fill the
remainder input parameters nevertheless. eg provides a don't care placeholder _
,
for these cases:
(eg vector
[1 2 3 4] [1 2 3 4]
[5 6 _ 8] vector?
[4 _ 5] vector?)
We can arbitrarily name a don't care parameter by prefixing its name with $
. A named don't care can also be bound with parts on the expected result:
(eg assoc-in
[{} [:a :b] {:eggs "boiled"}] => {:a {:b {:eggs "boiled"}}}
[_ $spam _] => map?
[_ _ $eggs] => {:a {:b $eggs}})
When writing the assertion, don't cares enable us to spend less time doing fillers, and the reader is able to better understand the focus of the assertion.
As a personal experience, writing tests often becomes an afterthought, because creating test boilerblate like a new test namespace, requiring test forms and functions under test is too much of a hassle, while being immersed on writting code. It makes sense to have test forms globally available that we use almost as often as defn
. Introducing set-eg!
! Call it at the development entrypoint of your program:
(require '[eg :refer [set-eg!]])
(set-eg!)
;=> :reloading ()
;=> #{#'clojure.core/eg #'clojure.core/ex #'clojure.core/ge}
Now use eg
, ge
, and ex
anywhere you want to create new tests!
PS - This functionality is only supported in Clojure.
It's possible to run only selected tests by using metadata ^:focus
on eg
or ge
:
(eg ^:focus false? [false] true)
There are some caveats to consider when using ^:focus
with ClojureScript:
- The tests report counts towards non focused tests, although assertions under such tests are not executed.
- Assertions for tests defined directly with
clojure.test/deftest
will be executed, despite the presence of focusedeg
, orge
tests.
Skip running certain tests or assertions using vanilla Clojure(Script) code:
#_(eg count ['(9 8 7)]
3)
(eg count
; ['(9 8 7)] 3
['(9 8 7 6)] 4)
Between eg
, and ge
, choose the form that is most convenient for your combination of function examples and use it only once for testing a function. For example, don't do this:
(ge inc [1] 2)
(ge inc [0] 1)
or this:
(eg inc [1] 2)
(ge inc [0] 1)
Remove WARNING: eg is a single segment namespace
warning with the compiler option single-segment-namespace
:
{:cljsbuild
{:builds
[{:compiler
{:warnings
{:single-segment-namespace false}}}]}}
Run your tests
Finally, run your tests as you normally would with clojure.test
.
Clojure tests in the REPL:
(clojure.test/run-all-tests)
; or
(clojure.test/run-tests some.ns)
Clojure tests in the terminal:
> lein test
ClojureScript tests in the REPL:
(cljs.test/run-all-tests)
; or
(cljs.test/run-tests some.ns)
Run eg's own tests
Run tests expected to pass, targeting Clojure:
> lein clj-test-pass
Run tests expected to pass, targeting ClojureScript JVM->nodejs:
> lein cljs-test-pass
Run tests expected to fail, targeting Clojure:
> lein clj-test-fail
Run tests expected to fail, targeting ClojureScript JVM->nodejs:
> lein cljs-test-fail
Run tests expected to pass, targeting ClojureScript JS:
> lein tach planck
# or
> lein tach lumo
Test libraries which work great with eg
- eftest – Eftest is a fast and pretty Clojure test runner.
- humane-test-output – Humane test output for clojure.test
License
Copyright (c) 2019 Carlos da Cunha Fontes
The Universal Permissive License (UPL), Version 1.0