Skip to content

Commit

Permalink
Fixes #4; adds between; fixes in (hash map); prep for 1.1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
seancorfield committed Dec 8, 2019
1 parent 7a0ea75 commit c538cf9
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 44 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Version 1.1.2 -- 2019-12-07

* Adds `between` and `between'` for inclusive and exclusive range checking.
* Fix `in` with a hash map to correctly detect failing cases.
* Add a first round of tests (finally!). Verified support for Clojure 1.8 (without Spec expectations). Verified full support for Clojure 1.9 and 1.10.1.
* Clean up `:require` .. `:refer` in README to list all public symbols. #4
* Fixes links in README. PR #3 (@marekjeszka)
* Add/improve docstrings. Add `^:no-doc` metadata for when cljdoc.org supports it.

# Version 1.1.1 -- 2019-01-14

* An expectation can now use a qualified keyword spec to test conformance of the actual value. Failures are reported with the spec explanation. #2
Expand Down
45 changes: 30 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A `clojure.test`-compatible version of the [classic Expectations testing library

## Where?

[![Clojars Project](https://clojars.org/expectations/clojure-test/latest-version.svg)](https://clojars.org/expectations/clojure-test)
[![Clojars Project](https://clojars.org/expectations/clojure-test/latest-version.svg)](https://clojars.org/expectations/clojure-test) [![cljdoc badge](https://cljdoc.org/badge/expectations/clojure-test)](https://cljdoc.org/d/expectations/clojure-test/CURRENT)

Try it out:

Expand All @@ -20,18 +20,25 @@ macro. This library has no dependencies, other than `clojure.test` itself, and
should be compatible with all existing `clojure.test`-based tooling in editors
and command-line tools.

Works with Clojure 1.8 and later. Spec expectations are only available on
Clojure 1.9 and later.

You can either use `deftest` from `clojure.test`, or `defexpect` from
this library to wrap your tests.

```clojure
(ns my.cool.project-test
(:require [clojure.test :refer [deftest is]]
[expectations.clojure.test :refer :all]))
(:require [clojure.spec.alpha :as s]
[clojure.test :refer [deftest is]]
[expectations.clojure.test
:refer [defexpect expect expecting
approximately between between' functionally
side-effects]]))

;; mix'n'match libraries:

(deftest mixed
(is 2 (+ 1 1))
(is (= 2 (+ 1 1)))
(expect even? (+ 1 1)))

;; simple equality tests:
Expand Down Expand Up @@ -61,9 +68,8 @@ this library to wrap your tests.

(defexpect named String (name :foo))

;; the expected outcome can be a Spec:
;; the expected outcome can be a Spec (require Clojure 1.9 or later):

;; assumes (require '[clojure.spec.alpha :as s])
(s/def ::value (s/and pos-int? #(< % 100)))
(defexpect small-value
(expect ::value (* 13 4)))
Expand Down Expand Up @@ -108,9 +114,9 @@ user=> (defexpect inequality (* 2 21) (+ 13 13 13))
#'user/inequality
user=> (inequality)

FAIL in (inequality) (.../README.md:67)
FAIL in (inequality) (.../README.md:113)
expected: (=? (* 2 21) (+ 13 13 13))
actual: 39
actual: (not (=? 42 39))
nil
```

Expand All @@ -124,7 +130,7 @@ user=> (defexpect indivisible odd? (+ 1 1))
#'user/indivisible
user=> (indivisible)

FAIL in (indivisible) (.../README.md:83)
FAIL in (indivisible) (.../README.md:129)
expected: (=? odd? (+ 1 1))
actual: (not (odd? 2))
nil
Expand Down Expand Up @@ -164,11 +170,11 @@ well-maintained, stable plugins for Leiningen and Boot, as well as an Emacs mode
the reality is that Clojure tooling is constantly evolving and most of those
tools -- such as the excellent [CIDER](https://cider.readthedocs.io/en/latest/),
[Cursive](https://cursive-ide.com/),
and the more recent [ProtoREPL](https://atom.io/packages/proto-repl)
and [Chlorine](https://atom.io/packages/chlorine) (both for Atom) --
[Chlorine](https://atom.io/packages/chlorine) (for Atom),
and [Cognitect's `test-runner`](https://github.com/cognitect-labs/test-runner) --
are going to focus on Clojure's built-in testing library first.
Support for the original form of Expectations, using unnamed tests, is
non-existent in Cursive, and can be problematic in other editors.
non-existent in Cursive, and can be problematic in other editors and tooling.

A whole ecosystem
of tooling has grown up around `clojure.test` and to take advantage of
Expand All @@ -195,17 +201,26 @@ to be aware of:
* Because of that, tests run when you decide, not at JVM shutdown (which is the default with Expectations).
* If you have [Paul Stadig's Humane Test Output](https://github.com/pjstadig/humane-test-output) on your classpath, it will be activated and failures reported by `=?` will be compatible with it, providing better reporting.
* Instead of the `in-context`, `before-run`, `after-run` machinery of Expectations, you can just use `clojure.test`'s fixtures machinery (`use-fixtures`).
* Instead of Expectations' concept of "focused" test, you can use metadata on tests and tell your test runner to "select" tests as needed (e.g., Leiningen's "test selectors", Boot's "filters").
* Instead of Expectations' concept of "focused" test, you can use metadata on tests and tell your test runner to "select" tests as needed (e.g., Leiningen's "test selectors", Boot's "filters", and `test-runner`'s `-i`/`-e` options).
* `freeze-time` and `redef-state` are not (yet) implemented.
* The undocumented `CustomPred` protocol is not implemented -- you can use plain `is` and extend `clojure.test`'s `assert-expr` multimethod if you need that level of control.

## Test & Development

To test, run `clj -A:test:runner`.
To test, run `clj -A:test:runner` (tests against Clojure 1.8).

Multi-version testing:

```
for v in 1.8 1.9 1.10
do
clojure -A:test:runner:$v
done
```

## TODO

Add tests(!) and more examples.
Add proper documentation for cljdoc.org.

## License & Copyright

Expand Down
8 changes: 6 additions & 2 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{:aliases
{:test {:extra-paths ["test"]}
{:deps {org.clojure/clojure {:mvn/version "1.8.0"}}
:aliases
{:1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}}
:1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}}
:1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.1"}}}
:test {:extra-paths ["test"]}
:humane {:extra-deps {pjstadig/humane-test-output {:mvn/version "RELEASE"}}}
:runner
{:extra-deps {com.cognitect/test-runner
Expand Down
12 changes: 2 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
<modelVersion>4.0.0</modelVersion>
<groupId>expectations</groupId>
<artifactId>clojure-test</artifactId>
<version>1.1.1</version>
<version>1.1.2</version>
<name>exp-clojure-test</name>

<description>A clojure.test-compatible version of the classic Expectations testing library.</description>
<url>https://github.com/clojure-expectations/clojure-test</url>
<licenses>
Expand All @@ -14,32 +13,27 @@
<url>http://www.eclipse.org/legal/epl-v10.html</url>
</license>
</licenses>

<developers>
<developer>
<name>Sean Corfield</name>
</developer>
</developers>

<scm>
<connection>scm:git:git@github.com:clojure-expectations/clojure-test.git</connection>
<developerConnection>scm:git:git@github.com:clojure-expectations/clojure-test.git</developerConnection>
<url>https://github.com/clojure-expectations/clojure-test</url>
<tag>5f0c9139245fbd320cbe787fcd76ef96007b29a4</tag>
</scm>

<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.10.0</version>
<version>1.8.0</version>
</dependency>
</dependencies>

<build>
<sourceDirectory>src</sourceDirectory>
</build>

<repositories>
<repository>
<id>clojars</id>
Expand All @@ -50,13 +44,11 @@
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>

<distributionManagement>
<repository>
<id>clojars</id>
<name>Clojars repository</name>
<url>https://clojars.org/repo</url>
</repository>
</distributionManagement>

</project>
72 changes: 56 additions & 16 deletions src/expectations/clojure/test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
(defn- bad-usage [s]
`(throw (IllegalArgumentException.
(str ~s " should only be used inside expect"))))
(defmacro in [& _] (bad-usage "in"))
(defmacro from-each [& _] (bad-usage "from-each"))
(defmacro more-of [& _] (bad-usage "more-of"))
(defmacro more-> [& _] (bad-usage "more->"))
(defmacro more [& _] (bad-usage "more"))
(defmacro ^:no-doc in [& _] (bad-usage "in"))
(defmacro ^:no-doc from-each [& _] (bad-usage "from-each"))
(defmacro ^:no-doc more-of [& _] (bad-usage "more-of"))
(defmacro ^:no-doc more-> [& _] (bad-usage "more->"))
(defmacro ^:no-doc more [& _] (bad-usage "more"))

(defn spec? [e]
(defn ^:no-doc spec? [e]
(and (keyword? e)
(try
(require 'clojure.spec.alpha)
Expand Down Expand Up @@ -78,15 +78,15 @@
(list '~'not (list '~'=? e# a#)))}))
r#)))

(defmacro ?
(defmacro ^:no-doc ?
"Wrapper for forms that might throw an exception so exception class names
can be used as predicates. This is only needed for more-> so that you can
thread exceptions into code that can parse information out of them, to be
used with various expect predicates."
[form]
`(try ~form (catch Throwable t# t#)))

(defn all-report
(defn ^:no-doc all-report
"Given an atom in which to accumulate results, return a function that
can be used in place of clojure.test/do-report, which simply remembers
all the reported results.
Expand All @@ -97,7 +97,28 @@
(swap! store update (:type m) (fnil conj []) m)))

(defmacro expect
"Translate Expectations DSL to clojure.test language."
"Translate Expectations DSL to clojure.test language.
These are approximate translations for the most basic forms:
`(expect actual)` => `(is actual)`
`(expect expected actual)` => `(is (= expected actual))`
`(expect predicate actual)` => `(is (predicate actual))`
`(expect regex actual)` => `(is (re-find regex actual))`
`(expect ClassName actual)` => `(is (instance? ClassName actual))`
`(expect ExceptionType actual)` => `(is (thrown? ExceptionType actual))`
`(expect spec actual)` => `(is (s/valid? spec actual))`
In addition, `actual` can be `(from-each [x coll] (computation-of x))`
or `(in set-of-results)` or `(in larger-hash-map)`.
Also, `expect` can be one of `(more predicate1 .. predicateN)`,
`(more-> exp1 expr1 .. expN exprN)` where `actual` is threaded through
each expression `exprX` and checked with the expected value `expX`,
or `(more-of binding exp1 val1 .. expN valN)` where `actual` is
destructured using the `binding` and then each expected value `expX`
is used to check each `valX` -- expressions based on symbols in the
`binding`."
([a] `(t/is ~a))
([e a] `(expect ~e ~a true ~e))
([e a ex?] `(expect ~e ~a ~ex? ~e))
Expand All @@ -120,8 +141,7 @@
(let [form `(~'expect ~e ~a)]
`(let [a# ~(second a)]
(cond (or (sequential? a#) (set? a#))
(let [report# t/do-report
all-reports# (atom nil)]
(let [all-reports# (atom nil)]
(with-redefs [t/do-report (all-report all-reports#)]
(doseq [~'x a#]
;; TODO: really want x evaluated here!
Expand All @@ -135,7 +155,7 @@
(doseq [r# (:fail @all-reports#)] (t/do-report r#)))))
(map? a#)
(let [e# ~e]
(expect e# (select-keys e# (keys a#)) ~ex? ~form))
(expect e# (select-keys a# (keys e#)) ~ex? ~form))
:else
(throw (IllegalArgumentException. "'in' requires map or sequence")))))

Expand Down Expand Up @@ -195,8 +215,8 @@
"Given a name (a symbol that may include metadata) and a test body,
produce a standard 'clojure.test' test var (using 'deftest').
(defexpect name expected actual) is a special case shorthand for
(defexpect name (expect expected actual)) provided as an easy way to migrate
`(defexpect name expected actual)` is a special case shorthand for
`(defexpect name (expect expected actual))` provided as an easy way to migrate
legacy Expectation tests to the 'clojure.test' compatibility version."
[n & body]
(if (and (>= 2 (count body))
Expand All @@ -205,12 +225,20 @@
`(t/deftest ~n ~@body)))

(defmacro expecting
"The Expectations version of clojure.test/testing."
"The Expectations version of `clojure.test/testing`."
[string & body]
`(t/testing ~string ~@body))

;; DSL functions copied from Expectations:
(defmacro side-effects [fn-vec & forms]
(defmacro side-effects
"Given a vector of functions to track calls to, execute the body.
Returns a vector of each set of arguments used in calls to those
functions. The specified functions will not actually be called:
only their arguments will be tracked, and the tracking versions of
those functions will not return useful values (so they should be
purely side-effecting functions, whose results are not used!)."
[fn-vec & forms]
(when-not (vector? fn-vec)
(throw (IllegalArgumentException.
"side-effects requires a vector as its first argument")))
Expand All @@ -227,6 +255,18 @@
([^double v ^double d]
(fn [x] (<= (- v (Math/abs d)) x (+ v (Math/abs d))))))

(defn between
"Given a pair of (numeric) values, return a predicate that expects its
argument to be be those values or between them -- inclusively."
[a b]
(fn [x] (<= a x b)))

(defn between'
"Given a pair of (numeric) values, return a predicate that expects its
argument to be (strictly) between those values -- exclusively."
[a b]
(fn [x] (< a x b)))

(defn functionally
"Given a pair of functions, return a custom predicate that checks that they
return the same result when applied to a value. May optionally accept a
Expand Down
Loading

0 comments on commit c538cf9

Please sign in to comment.