Bolster your Clojure[Script] test suite confidence.
Clofidence is a test coverage tool for Clojure and ClojureScript. It will instrument your codebase using ClojureStorm or ClojureScriptStorm, then run your tests and generate a test coverage report.
Add an alias to your deps.edn
like this :
{....
:aliases
{:test {...}
:clofidence {:classpath-overrides {org.clojure/clojure nil}
:extra-deps {com.github.flow-storm/clojure {:mvn/version "LATEST"} ; >= 1.11.1-15
com.github.flow-storm/clofidence {:mvn/version "LATEST"}}
:exec-fn clofidence.main/run
:exec-args {:report-name "MyApp"
:test-fn cognitect.test-runner.api/test
:test-fn-args [{}]}
:jvm-opts ["-Dclojure.storm.instrumentOnlyPrefixes=my-app"
"-Dclojure.storm.instrumentSkipPrefixes=my-app.unwanted-ns1,my-app.unwanted-ns2"
"-Dclojure.storm.instrumentSkipRegex=.*test.*"
]}}}
Please make sure you have the latest versions of ClojureStorm and Clofidence.
With Clofidence configured, every time you want to generate a coverage report you run :
$ clj -X:test:clofidence
After running the tests, it should generate a folder (by default called clofidence-output) with a index.html pointing to every ns details. Click here to download the Clofidence report for the ClojureScript compiler v1.11.60 as an example.
The example above assumes your aliases contain a :test
alias that will put the tests paths and test-runner
on the classpath, but this will depend on your particular test setup.
There is a lot going on in the configuration, so let's walk over it :
- First, we need to disable the official Clojure compiler, since we are going to replace it with ClojureStorm
- Next, we add the latest ClojureStorm and Clofidence dependencies
:exec-fn
and:exec-args
tells the Clojure cli what Clofidence main entry point is and with what arguments it should call it:report-name
just configures the report header and file name:test-fn
tells Clofidence what function will run your tests:test-fn-args
are the arguments to the function defined in:test-fn
:output-folder
there an optional output folder, to provide a folder name, otherwiseclofidence-output
will be used
- Finally we need to tell Clofidence which namespaces to include and which to skip for the coverage
instrumentOnlyPrefixes
should be a comma separated list of namespaces prefixes to include. Addingmy-app
will include everything undermy-app.core
andmy-app.web.routes
.instrumentSkipPrefixes
can be used in the same way, but to skip unwanted namespaces.instrumentSkipRegex
should be a regex to match namespaces to skip
Minimum requirements :
- nodejs >= 18.0.0 (when running tests with node)
- shadow-cljs >= 2.25.4
Add an alias to your deps.edn
like this :
{....
:aliases
{:test {...}
:clofidence {:classpath-overrides {org.clojure/clojurescript nil}
:extra-deps {thheller/shadow-cljs {:mvn/version "2.28.10" :exclusions [org.clojure/clojurescript]}
com.github.flow-storm/clojurescript {:mvn/version "LATEST"} ;; >= 1.11.132-6
com.github.flow-storm/clofidence {:mvn/version "LATEST"}}
:exec-fn clofidence.main/run-cljs
:exec-args {:report-name "MyApp"}
:jvm-opts ["-Dcljs.storm.instrumentOnlyPrefixes=org.foo,dev"
"-Dcljs.storm.instrumentSkipPrefixes=dev.skipped"
"-Dcljs.storm.instrumentSkipRegex=.*test.*"
"-Dcljs.storm.instrumentEnable=true"]}}}
Modify your shadow-cljs.edn test builds (nodejs and browser currently supported) like this :
{:deps {:aliases [:clofidence :test]}
:dev-http {8021 "out/test"}
:builds
{:test-browser {:target :browser-test
:test-dir "out/test"
:runner-ns clofidence.shadow-test.browser
:devtools {:preloads [clofidence.storm]}}
:test-node {:target :node-test
:output-to "out/node-tests.js"
:main clofidence.shadow-test.node/main
:devtools {:preloads [clofidence.storm]}}}}
And then spawn a clofidence server with :
$ clj -X:test:clofidence
You should now be able to run your nodejs or browser tests as you normally do with shadow-cljs which for the browser will be something like :
$ npx shadow-cljs compile :test-browser # compile the tests
$ npx shadow-cljs server # start the dev server
$ firefox http://localhost:8021 # run the tests
while for nodejs will be something like :
$ npx shadow-cljs compile :test-node # compile the tests
$ node out/node-tests.js # run the tests
After the tests run on the JS runtime, the results will be sent to your Clofidence server (the one you spawned before) which will generate the reports on disk in its current folder.
You can now open your tests reports with something like :
$ firefox clofidence-output/index.html
The example above assumes your aliases contain a :test
alias that will put the tests paths
on the classpath, but this will depend on your particular test setup.
There is a lot going on in the configuration, so let's walk over it :
- First, we need to disable the official ClojureScript compiler, since we are going to replace it with ClojureScriptStorm
- Next, we add the latest ClojureScriptStorm, Clofidence and shadow-cljs dependencies, excluidng the official ClojureScript compiler that comes with shadow-cljs
:exec-fn
and:exec-args
tells the Clojure cli to run the clofidence server and with what arguments it should call it:report-name
just configures the report header and file name:output-folder
there an optional output folder, to provide a folder name, otherwiseclofidence-output
will be used
- Finally we need to tell Clofidence which namespaces to include and which to skip for the coverage
instrumentOnlyPrefixes
should be a comma separated list of namespaces prefixes to include. Addingmy-app
will include everything undermy-app.core
andmy-app.web.routes
.instrumentSkipPrefixes
can be used in the same way, but to skip unwanted namespaces.instrumentSkipRegex
should be a regex to match namespaces to skip
Here you can download and see the Clofidence report for the ClojureScript compiler v1.11.60.
Extracting that tarball will give you a folder containing a index.html with an overview pointing to the rest of the files which contains each namespace details.
The Total forms hit rate shows how many top level forms were at least touched once by the tests, out of all the instrumented forms.
The Total sub forms hit rate shows how many sub-expressions were hit out of all instrumented ones.
The header overview shows one bar per namespace, and how much of it has been covered.
The size of the bars is proportional to the amount of code in the namespaces.
Clicking on the namespace name will take you to the namespace details.
Right after the overview, the report includes a list of all your instrumented forms, grouped by namespaces and sorted by coverage.
Forms will have a green background if the coverage is > 50%, a yellow background between 1% and 50% and a red background for forms with 0% coverage.
Each forms contains details of what sub-expressions were hit at least once. This is useful to identify parts of your forms that aren't being exercised by the tests.
Hitted sub-expressions will be highlighted in green while hittable but not hit ones will be highlighted in red. Skimming over the red ones should give you an overview of uncovered conditional branches, uncovered functions signatures, etc.
By default, only forms with the first symbol name being one of : defn
, defn-
, defmethod
, extend-type
, extend-protocol
, deftype
, defrecord
and def
forms which define functions will be included.
If you have other types of forms (like the ones defined by some macros), you can include them by using :extra-forms
in the configuration
parameters. It takes a set of symbols like :extra-forms #{defroutes}
.
If instead of using the default and allowing some extra forms you prefer the other way around, you can use the :block-forms
config.
If you config it like :block-forms #{}
, all forms will be included (with the exception of a few, see next) and you can block some
by adding them to the set, like :block-forms #{my-annoying-form}
.
Even with :block-forms #{}
there are some forms which are always excluded, which are ns
, defprotocol
, quote
, comment
, def
with constants
which doesn't make sense.