Lazytest is a BDD test framework for Clojure: https://github.com/NoahTheDuke/lazytest
#Lazytest
1 messages · Page 1 of 1 (latest)
i cut a new release of lazytest yesterday, including a feature i've wanted in clojure core forever
(defn test-fn
{:test #(expect (= 0 (test-fn 1)))}
[a]
(+ a a))
(defn test-test-case
{:test (it "test case example"
(expect (= 1 (test-test-case 1))))}
[a]
(+ a a))
(defn test-suite
{:test (suite
(test-seq
[(it "test-seq example" (expect (= 1 (test-suite 1))))
(it "test-seq example two" (expect (= 0 (test-suite 1))))]))}
[a]
(+ a a))
(defn test-describe
([a]
(+ a a))
{:test (describe "top level"
(it "test-describe example" (expect (= 1 (test-describe 1))))
(it "test-describe example two" (expect (= 0 (test-describe 1)))))})
that last one really surprises me, how does that syntax work with the test at the end of the defn after the fn tail?
all of those produce lazytest tests, and are appropriately run if they're included on the test path
the attr-map can be at the end of defn forms if the bodies are wrapped in parentheses
:arglists '([name doc-string? attr-map? [params*] prepost-map? body]
[name doc-string? attr-map? ([params*] prepost-map? body)+ attr-map?])
yeah, it's really neat! it means your code bundles lazytest, which is a bummer, but it also means you get "Rich Comment Forms" directly with the code itself
i ran into this idea recently with Pyret (by the Brown University PLT folks), where any function can have a where: clause at the end which acts as a unit test: ```
fun sum(l):
cases (List) l:
| empty => 0
| link(first, rest) => first + sum(rest)
end
where:
sum([list: ]) is 0
sum([list: 1, 2, 3]) is 6
end
that seems cool
cut a new release to add --watch support, so you can run your test suite every time you change a file
a lil sneak peak of what i'm working on:
(given [state (volatile! [])]
(describe "not order dependent"
{:context [(after (vswap! state conj :after))
(before (vswap! state conj :before))]}
(expect-it "temp" true))
(expect-it "tracks correctly"
(= [:before :after] @state)))
with this and an around helper, i think i'd consider the library on par with clojure.test. fixtures have been bugging me for a while, but cracking it as simply metadata on suites is the key, i think
okay, i'm a fucking god lol
(defn vconj! [volatile value]
(vswap! volatile conj value))
(defdescribe complex-context-test
(given [state (volatile! [])]
(describe "top level"
{:context [(before (vconj! state :before-top))
(before-each (vconj! state :before-each-top))
(after-each (vconj! state :after-each-top))
(after (vconj! state :after-top))]}
(describe "middle level"
{:context [(before (vconj! state :before-middle))
(before-each (vconj! state :before-each-middle))
(after-each (vconj! state :after-each-middle))
(after (vconj! state :after-middle))]}
(describe "bottom level"
{:context [(before (vconj! state :before-bottom))
(before-each (vconj! state :before-each-bottom))
(after-each (vconj! state :after-each-bottom))
(after (vconj! state :after-bottom))]}
(expect-it "temp 1" (vconj! state :bottom-expect-1))
(expect-it "temp 2" (vconj! state :bottom-expect-2)))))
(expect-it "tracks correctly"
(= [:before-top
:before-middle
:before-bottom
:before-each-top
:before-each-middle
:before-each-bottom
:bottom-expect-1
:after-each-bottom
:after-each-middle
:after-each-top
:before-each-top
:before-each-middle
:before-each-bottom
:bottom-expect-2
:after-each-bottom
:after-each-middle
:after-each-top
:after-bottom
:after-middle
:after-top] @state))))
okay, i've done it. i have full before/after support plus namespace-level fixtures, which i think covers all of the existing clojure.test functionality
the fixture/context support works on suites and on text cases, so anywhere you can write {:focus true}, you can add lexically scoped fixtures
i played with full run support, but that's really opaque and feels naughty
if someone wants to do that, they can call main/run themselves.
i think the last thing i wanna do before i consider this fairly complete is deprecating the given macro in favor of :let in the suite metadata:
(describe "example"
{:let [foo 100]}
(it "works"
(expect (= 100 foo))))
this would roughly match how doseq and for work, and would magically do the right thing instead of given, which requires fiddling
I've decided to pull the trigger on 1.0. I use this exclusively in splint, and having rewritten the internals to be more idiomatic (maps with children instead of nested seqs with metadata), I think this ready for prime-time.
- Added before, after, before-each, and after-each hooks. These act as suite-specific fixtures, allowing for creating or setting temporary data across nested suites or test cases. before and after run where they're called, before-each and after-each are gathered in declaration order and called on each test-case.
(defdescribe context-test
(let [state (volatile! [])]
(describe "it runs both"
{:context [(before (vswap! state conj :before))
(after (vswap! state conj :after))]}
(expect-it "temp" true))
(expect-it "tracks correctly"
(= [:before :after] @state))))```
- Added around hook, which acts like a clojure.test fixture, taking a no-arg function of the nested suites/tests and letting you write arbitrary code inside (which handles binding needs):
```clojure
(describe "it works"
{:context [(around [f]
(binding [*foo* 100]
(f)))]}
(expect-it "matches" (= 100 *foo*)))```
- Added set-ns-context! which alters the namespace's metadata to hold the provided context data, allowing for clojure.test-like use-fixture functionality.
i think at this point lazytest has all of the features of clojure.test
extensible reporters, custom assertion macros, running specific tests or a set of namespaces or the whole thing, test selectors through metadata like cognitect's test-runner, "testing" blocks, etc
lol idk why i did this but i implemented a bunch of "interfaces" for lazytest
(defcontext interface-context-test
(specify "defcontext works"
(expect true "expect works inside"))
(context "context works"
(specify "specify works"
(expect true))))
(defsuite interface-tdd-test
(suite "defsuite works"
(expect true "expect works inside"))
(suite "suite works"
(test "specify works"
(expect true))))
(deftest interface-deftest-test
(is true "expect works inside")
(testing "testing works"
(is (= 7 (+ 3 4)) "is works"))
(testing "are works"
(are [x y] (= x y)
2 (+ 1 1)
4 (* 2 2))))
(facts interface-midje-test
(fact "top-level facts works"
(expect true "expect works inside"))
(facts "facts works"
(fact "fact works"
(expect true))))
i briefly thought about implementing midje's "given => expected-result" form, but that's really annoying and midje is actually super complex so idk that i want to even act like lazytest can handle it
released v1.1.0 which supports a bunch of these:
https://cljdoc.org/d/io.github.noahtheduke/lazytest/1.1.0/api/lazytest.experimental.interfaces.qunit
(ns noahtheduke.example-test
(:require
[lazytest.interfaces.qunit :refer [prep-ns-suite! module! test! assert!]]))
(prep-ns-suite!)
(module! "Group A")
(test! "foo"
(assert! (true? (pos? 1)))
(assert! (false? (pos? 0)) "Expected to be false"))
I've added doc-test support, so you can pass in markdown files with --md and they'll be scanned and treated as tests:
# Cool stuff is happening!
~~~clojure
(adder 5 6)
;; => 11
~~~
is treated as (defdescribe test-12345 (it "Cool stuff is happening!" (expect (= 11 (adder 5 6))))) And you can skip tests with Markdown's info-strings:
(System/exit 1)
I've also added preliminary support for Expectations v2: (it "just works" (expect String (name :foo)))
Literate tests! 😍
these are all run in CI right now
released v1.4.0: https://github.com/NoahTheDuke/lazytest/releases/tag/v1.4.0
a smaller release, but it cleans up :output and :reporter so all external-facing functions take :output which is converted to a reporting function in :reporter, and it allows for trailing args as the paths: clojure -M:dev:test test/unit test/integration
along with a fair amount of documentation
been a minute since i posted in here. i released 1.7.0 today which makes Expectations v2 a first class namespace
i love having expectations of being lazy 🥰
i released 2.0.0: https://github.com/NoahTheDuke/lazytest/releases/tag/v2.0.0
this one includes hooks (plugin support), adds a new context function around-each, fixes expect-it to respect context functions, and cleans up the order of all of the context functions in general