If the local bindings will never change, then why not just use
(binding [whatever-setup ...] ...) wrapping the individual test bodies
that need such setup? (Where explicit tear-down is required, you'd
need try ... finally as well, or better yet a macro like with-open,
but using binding instead of let, to abstract out the repeated aspects
of such code. Common setups could become more macros, e.g.
(with-foo-whatsit [some-symbol some-other-symbol] ...).)

If the bindings are to stateful stuff that a sequence of tests will
alter, though, you have more problems. Possible improvements:

* Redesign the whole thing to be more functional and less stateful.

* At least, move as much into pure functions as possible; the pure
functions are easy to test.

* The (remaining) sequences of tests on state will have to run
sequentially, so combine them into a single test that calls the
smaller ones.

You'll end up with something like:

(def results (atom {}))

(def tests (atom []))

(def foo nil)

(def bar nil)

(defmacro deftest ... )

(deftest foo nil
  [foo (initialize-some-resource)
   bar (initialize-another)]
    (the test goes here)))

and that produces something like

(do
  (defn foo []
    (binding [foo (initialize-some-resource)]
      (try
        (binding [bar (initialize-another)]
          (try
            (deliver (@results foo) (the test goes here))
            (finally (.close bar))))
        (catch Throwable _ (deliver (@results foo) false))
        (finally (.close foo)))))
  (swap! results assoc foo (promise))
  (swap! tests conj foo))

whereas

(deftest bar foo ...)

is similar, but with foo instead of nil for the second argument the
bar function body gets wrapped in:

(if @(@results foo)
  (do
    (body that would have been)
    (with nil second arg))
  (deliver (@results bar) false))

so when bar is run it blocks until foo has run, and short-circuits to
failing if foo failed, but runs if foo succeeded.

And then there'd be

(deftest baz :subtest ...)

which expands without swap!s or a wrapper -- it just becomes a
function like foo and nothing else. A later test body can do setup,
call baz as a function along with several other subtests, and do
teardown, e.g.

(deftest quux ...
  ...
  (and (baz) (baz2) (baz3)))

which, obviously, short-circuits to failure if any of these fails and
otherwise runs them all and returns baz3's result.

Lastly, you'd have

(doall (apply pcalls @tests))

to run the tests and

(report)

after that, with

(defn report
  (doseq [[test result] @results]
    (println (:name (meta test)) " - " @result)))

or whatever. If you want more detailed reports, deftest could put
additional stuff into the metadata of the functions (and have
additional arguments, if necessary).

The above framework seems like it would do what you want: allow tests
to have setup and teardown and simplify that, make the stuff thus set
up be dynamic bindings visible to subsidiary functions, allow tests to
depend on earlier tests and block until the earlier tests complete,
and allow a sequence of tests to be run within a single set-up
environment run sequentially, in a single thread, in that environment.
The major downside is that pcalls is optimized for cpu-bound jobs; if
the tests are I/O-bound you'll want to change pcalls to something
based around Executor instead, with a thread pool however large you
want. The other is that tests that must be run in the same
environment, with just one setup before all the tests and one teardown
after, will be a single unit as far as parallelism goes and will stop
on the first failed test. The framework can be modified to let
subtests create separate result entries, though, and using (let [a
(baz) b (baz2) c (baz3)] (and a b c)) will let you run more subtests
even if one fails, when you know the setup environment won't have been
borked by the failures. Also, tests that should get an exception
require you to explicitly catch the exception exception and return
true, e.g. (try (this-should-throw-something) false (catch
FooException _ true)). You could add a bunch of (expect ...) macros to
simplify further the writing of tests.

If you're trying to use an existing testing framework like midje,
though, you're probably SOL unless the framework developer provides
their own parallel testing support or you can understand the framework
code well enough to marry it to something like the above.

-- 
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to