Git Product home page Git Product logo

test-report's Introduction

test-report

CircleCI

test-report is a tool for customizing clojure.test output.

Installation

The easiest way to get started is to add the lein-test-report plugin to your Leiningen project map. As it's only used in tests, it's best to add it only to the :test profile:

:profiles {:test {:plugins [[lein-test-report "0.2.0"]]}}

Usage

test-report hooks into the clojure.test/run-tests function, so will automatically be included when running lein test.

By default, the configuration is identical to the clojure.test defaults, so you won't see any difference. To start customizing the output, you'll need to provide some reporters or summarizers.

Reporters

As your tests run, clojure.test passes messages to clojure.test/report. The default implementation prints relevant details to stdout. Leiningen enhances the output by including commands that can be used to re-run individual tests.

To override this output, you can provide a collection of reporters to test-report, which are called sequentially with each message reported during the test run. For example, to pretty-print the message to stdout, add the following to the :test profile:

:injections [(require 'clojure.pprint)]
:test-report {:reporters [clojure.pprint/pprint]}

Notice how namespaces not already used in your tests need to be required in an injection.

The resulting output, which might be useful when developing your own reporters, looks something like

$ lein test
{:type :begin-test-ns,
 :ns #object[clojure.lang.Namespace 0x66fdec9 "example.test"],
 :time 45906184724820}
{:type :begin-test-var,
 :var #'example.test/arithmetic,
 :time 45906212298330}
{:type :pass,
 :expected (= 4 (+ 2 2)),
 :actual
 (#object[clojure.core$_EQ_ 0x3016fd5e "clojure.core$_EQ_@3016fd5e"]
  4
  4),
 :message nil,
 :time 45906216409445,
 :context ("with positive integers" "addition")}
{:type :pass,
 :expected (= 7 (+ 3 4)),
 :actual
 (#object[clojure.core$_EQ_ 0x3016fd5e "clojure.core$_EQ_@3016fd5e"]
  7
  7),
 :message nil,
 :time 45906232042175,
 :context ("with positive integers" "addition")}
{:type :end-test-var,
 :var #'example.test/arithmetic,
 :time 45906241926006}
{:type :end-test-ns,
 :ns #object[clojure.lang.Namespace 0x66fdec9 "example.test"],
 :time 45906243742590}
{:test 1,
 :pass 0,
 :fail 0,
 :error 0,
 :type :summary,
 :time 45906248308974}

Note that the final summary message does not have accurate counts; the original implementation of clojure.test/report is not being called, so the counters are not being incremented. When implementing your own reporter, you may need to call clojure.test/inc-report-counter yourself.

The messages passed to the reporters have some extra information compared to those originally passed to clojure.test/report. A nanosecond offset :time is added to all messages, which can be used to calculate the test execution time. Test results (:pass, :fail, or :error messages) also have :context, which is the list of strings passed to clojure.test/testing, ordered from innermost to outermost.

Summarizers

To produce output based on aggregate test results (for example, a JUnit XML report), reporters would have to keep track of previous messages in a mutable data structure. In this case, it may be simpler to use a summarizer. Each summarizer is called at the end of the test run, and passed a vector of all the messages that were reported. For example, to pretty-print the messages to stdout:

:injections [(require 'clojure.pprint)]
:test-report {:summarizers [clojure.pprint/pprint]}
$ lein test

Testing example.test

Ran 1 tests containing 2 assertions.
0 failures, 0 errors.
[{:type :begin-test-ns,
  :ns #object[clojure.lang.Namespace 0x1b73be9f "example.test"],
  :time 46558525944477}
 {:type :begin-test-var,
  :var #'example.test/arithmetic,
  :time 46558529380772}
 {:type :pass,
  :expected (= 4 (+ 2 2)),
  :actual
  (#object[clojure.core$_EQ_ 0x5ccbeb64 "clojure.core$_EQ_@5ccbeb64"]
   4
   4),
  :message nil,
  :time 46558532202149,
  :context ("with positive integers" "addition")}
 {:type :pass,
  :expected (= 7 (+ 3 4)),
  :actual
  (#object[clojure.core$_EQ_ 0x5ccbeb64 "clojure.core$_EQ_@5ccbeb64"]
   7
   7),
  :message nil,
  :time 46558533416008,
  :context ("with positive integers" "addition")}
 {:type :end-test-var,
  :var #'example.test/arithmetic,
  :time 46558534111792}
 {:type :end-test-ns,
  :ns #object[clojure.lang.Namespace 0x1b73be9f "example.test"],
  :time 46558534219611}
 {:test 1,
  :pass 2,
  :fail 0,
  :error 0,
  :type :summary,
  :time 46558534317550}]

The test-report.summary/summarize function collates the messages into a more usable form:

:injections [(require 'clojure.pprint 'test-report.summary)]
:test-report {:summarizers [(comp clojure.pprint/pprint test-report.summary/summarize)]}
$ lein test

Testing example.test

Ran 1 tests containing 2 assertions.
0 failures, 0 errors.
{:namespaces
 [{:ns
   #object[clojure.lang.Namespace 0x31000e60 "example.test"],
   :time 7656556,
   :tests
   [{:var #'example.test/arithmetic,
     :time 2690626,
     :results
     ({:type :pass,
       :expected (= 4 (+ 2 2)),
       :actual
       (#object[clojure.core$_EQ_ 0x42f8285e "clojure.core$_EQ_@42f8285e"]
        4
        4),
       :message nil,
       :context ("with positive integers" "addition")}
      {:type :pass,
       :expected (= 7 (+ 3 4)),
       :actual
       (#object[clojure.core$_EQ_ 0x42f8285e "clojure.core$_EQ_@42f8285e"]
        7
        7),
       :message nil,
       :context ("with positive integers" "addition")})}],
   :summary {:test 1, :assertion 2, :fail 0, :error 0, :pass 2}}],
 :summary {:test 1, :assertion 2, :fail 0, :error 0, :pass 2}}

License

Copyright © 2017 Red Badger

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

test-report's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

kubrack samharad

test-report's Issues

Shade dependencies

Injecting dependencies (robert.hooke, clojure.data.xml, etc) into tests is liable to cause version clashes. If possible, test-report and test-report-junit-xml should be distributed as uberjars with shaded dependences (like those produced by the Maven Shade Plugin).

Test results outside of :begin-test-var/:end-test-var are ignored

It's possible to be results outside of :begin-test-var/:end-test-var, for example of :error type in case of exception in a test fixture. Currently such results are ignored.

How to reproduce:

$ cat test/test_report/str.clj
(ns test-report.str
  (:require [clojure.test :refer [deftest is use-fixtures]]
            [test-report.summary :refer [summarize]]
            [test-report.core :refer [activate]]
            [clojure.pprint :refer [pprint]]))

(activate {:summarizers [(comp pprint summarize)]})
(use-fixtures :each (fn [f] (/ 1 0) (f)))
(deftest will-never-ran (is true))

$ lein test :only test-report.str

Testing test-report.str

ERROR in (test-report.str) (Numbers.java:158)
Uncaught exception in test fixture
expected: nil
  actual: java.lang.ArithmeticException: Divide by zero
 at clojure.lang.Numbers.divide (Numbers.java:158)
    clojure.lang.Numbers.divide (Numbers.java:3808)
    test_report.str$eval413$fn__414.invoke (str.clj:8)
    clojure.test$compose_fixtures$fn__7977$fn__7978.invoke (test.clj:693)
    clojure.test$default_fixture.invokeStatic (test.clj:686)
    clojure.test$default_fixture.invoke (test.clj:682)
    clojure.test$compose_fixtures$fn__7977.invoke (test.clj:693)
    clojure.test$test_vars$fn__8005.invoke (test.clj:734)
    clojure.test$default_fixture.invokeStatic (test.clj:686)
    clojure.test$default_fixture.invoke (test.clj:682)
    clojure.test$test_vars.invokeStatic (test.clj:730)
    clojure.test$test_all_vars.invokeStatic (test.clj:736)
    clojure.test$test_ns.invokeStatic (test.clj:757)
    clojure.test$test_ns.invoke (test.clj:742)
    user$eval85$fn__196.invoke (form-init1833884939756857322.clj:1)
    clojure.lang.AFn.applyToHelper (AFn.java:156)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:648)
    clojure.core$apply.invoke (core.clj:641)
    leiningen.core.injected$compose_hooks$fn__19.doInvoke (form-init1833884939756857322.clj:1)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:646)
    clojure.core$apply.invoke (core.clj:641)
    leiningen.core.injected$run_hooks.invokeStatic (form-init1833884939756857322.clj:1)
    leiningen.core.injected$run_hooks.invoke (form-init1833884939756857322.clj:1)
    leiningen.core.injected$prepare_for_hooks$fn__24$fn__25.doInvoke (form-init1833884939756857322.clj:1)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.AFunction$1.doInvoke (AFunction.java:29)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$map$fn__4785.invoke (core.clj:2646)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.Cons.next (Cons.java:39)
    clojure.lang.RT.boundedLength (RT.java:1749)
    clojure.lang.RestFn.applyTo (RestFn.java:130)
    clojure.core$apply.invokeStatic (core.clj:648)
    clojure.test$run_tests.invokeStatic (test.clj:767)
    clojure.test$run_tests.doInvoke (test.clj:767)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:646)
    clojure.core$apply.invoke (core.clj:641)
    test_report.core$collect_messages.invokeStatic (core.clj:37)
    test_report.core$collect_messages.doInvoke (core.clj:34)
    clojure.lang.RestFn.applyTo (RestFn.java:139)
    clojure.core$apply.invokeStatic (core.clj:646)
    clojure.core$apply.invoke (core.clj:641)
    test_report.core$activate$fn__399$fn__403.invoke (core.clj:55)
    clojure.lang.AFn.applyToHelper (AFn.java:152)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:646)
    clojure.core$with_bindings_STAR_.invokeStatic (core.clj:1881)
    clojure.core$with_bindings_STAR_.doInvoke (core.clj:1881)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    test_report.core$activate$fn__399.doInvoke (core.clj:55)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:648)
    clojure.core$apply.invoke (core.clj:641)
    robert.hooke$compose_hooks$fn__295.doInvoke (hooke.clj:40)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:646)
    clojure.core$apply.invoke (core.clj:641)
    robert.hooke$run_hooks.invokeStatic (hooke.clj:46)
    robert.hooke$run_hooks.invoke (hooke.clj:45)
    robert.hooke$prepare_for_hooks$fn__300$fn__301.doInvoke (hooke.clj:54)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.AFunction$1.doInvoke (AFunction.java:29)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:646)
    clojure.core$apply.invoke (core.clj:641)
    user$eval85$fn__208$fn__259.invoke (form-init1833884939756857322.clj:1)
    user$eval85$fn__208$fn__209.invoke (form-init1833884939756857322.clj:1)
    user$eval85$fn__208.invoke (form-init1833884939756857322.clj:1)
    user$eval85.invokeStatic (form-init1833884939756857322.clj:1)
    user$eval85.invoke (form-init1833884939756857322.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:6927)
    clojure.lang.Compiler.eval (Compiler.java:6917)
    clojure.lang.Compiler.load (Compiler.java:7379)
    clojure.lang.Compiler.loadFile (Compiler.java:7317)
    clojure.main$load_script.invokeStatic (main.clj:275)
    clojure.main$init_opt.invokeStatic (main.clj:277)
    clojure.main$init_opt.invoke (main.clj:277)
    clojure.main$initialize.invokeStatic (main.clj:308)
    clojure.main$null_opt.invokeStatic (main.clj:342)
    clojure.main$null_opt.invoke (main.clj:339)
    clojure.main$main.invokeStatic (main.clj:421)
    clojure.main$main.doInvoke (main.clj:384)
    clojure.lang.RestFn.invoke (RestFn.java:421)
    clojure.lang.Var.invoke (Var.java:383)
    clojure.lang.AFn.applyToHelper (AFn.java:156)
    clojure.lang.Var.applyTo (Var.java:700)
    clojure.main.main (main.java:37)

Ran 0 tests containing 1 assertions.
0 failures, 1 errors.
{:namespaces
 [{:ns #object[clojure.lang.Namespace 0x45815ffc "test-report.str"],
   :time 44671979,
   :tests [],
   :summary {:test 0, :assertion 0, :fail 0, :error 0, :pass 0}}],
 :summary {:test 0, :assertion 0, :fail 0, :error 0, :pass 0}}
Tests failed.

it should be at list with right statistic:

{:namespaces
 [{:ns #object[clojure.lang.Namespace 0x45815ffc "test-report.str"],
   :time 44671979,
   :tests [],
   :summary {:test 1, :assertion 1, :fail 0, :error 1, :pass 0}}],
 :summary {:test 1, :assertion 1, :fail 0, :error 1, :pass 0}}

even maybe it worth to pass such results into :tests of :namespaces (with only :results key):

{:namespaces
 [{:ns #object[clojure.lang.Namespace 0x45815ffc "test-report.str"],
   :time 44671979,
   :tests 
   [{:results
     [{:file "Numbers.java",
       :line 158,
       :type :error,
       :expected nil,
       :actual #error { ... }
       :message "Uncaught exception in test fixture",
       :context ()}]}],
   :summary {:test 1, :assertion 1, :fail 0, :error 1, :pass 0}}],
 :summary {:test 1, :assertion 1, :fail 0, :error 1, :pass 0}}

Composed tests are not supported

Tests may be composed, for example

(deftest addition
  (is (= 4 (+ 2 2)))
  (is (= 7 (+ 3 4))))

(deftest subtraction
  (is (= 1 (- 4 3)))
  (is (= 3 (- 7 4))))

(deftest arithmetic
  (addition)
  (subtraction))

(defn test-ns-hook []
  (arithmetic))

This means that :begin-test-var messages can be nested:

{:type :begin-test-var, :var #'example.test/arithmetic}
{:type :begin-test-var, :var #'example.test/addition}
...
{:type :end-test-var, :var #'example.test/addition}
{:type :begin-test-var, :var #'example.test/subtraction}
...
{:type :end-test-var, :var #'example.test/subtraction}
{:type :end-test-var, :var #'example.test/arithmetic}

This is not supported by test-report.summary/summarize (which assumes that only results appear between :begin- and :end-test-var), and therefore not handled well in the JUnit XML output.

It doesn't appear to be particularly widely used so is probably not a high priority to fix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.