;; Note: originally I was just going to write Stuart, but I think input from
anybody on the list could be valuable, so I'm CC'ing Clojure

Hi Stuart,

I've been working on an RSpec-like library for Clojure called Specjure. It
has gone through many iterations and the one that is currently up on github
is not something I am satisfied with.

As I get closer to something I like, it seems that what I will end up with
will end up with will look look like where test-is seems to be heading. So,
rather than creating duplicate work I'd like to get your opinion on what I
have in mind. If you like it, I'd be glad to contribute to test-is rather
than creating a duplicate library.

This is what I'm thinking, let me explain: http://gist.github.com/41468
(If you are familiar with RSpec, here is the equivalent RSpec example
http://rspec.info/examples.html)

First off, I renamed deftest to test (note this is a small design choice
that I think is a good idea, but wouldn't care too much about.) I tend to
write a lot of tests, and having the main keywords for tests be something
short is a nice way to encourage writing many tests. The "is" macro in
test-is follows this "short name for frequently used form" theme (as does
def instead of define, and defn instead of define-function in clojure), and
I think it's appropriate to do the same for deftest->test.

Another change is that the "test" macro can take a symbol, a string, or both
as arguments. This allows for more specification/RSpec/BDD-like tests to be
created (I noticed you're experimenting with something similar in
clojure-contrib tests, in the form of test-that). Also, if somebody wanted
to just do non-specification style tests by using symbols then they are free
to do so.

There is also a new "context". This should be familiar to  you if  you've
ever used RSpec or other BDD libs. It allows for arbitrary nesting of
contexts. Inside of a context, you can use "test". Any test forms used in a
context will inherit its description, prepending it to its own description.

Quick aside: If you use a symbol, it's resolved name will be used in the
description, ie:

;; the description string here would be "my-ns/my-fn run concurrently
returns foo"
(ns my-ns)
(context my-fn "run concurrently"
  (test "returns foo"
    (is (= "foo" (my -fn :concurrent)))))

You can also use the "before" and "after" forms. Any code in these will be
captured into functions, and run before running any "test"s defined in the
context. This is useful for things like database fixtures that must clean
and repopulate database tables before running tests.

The before/after forms are also inherited when nesting contexts. Another two
forms are $get and $assoc!
$assoc! lets you set key/value pairs to a map that is created for each test,
and $get lets you access the map. This is useful for things like setting up
values in a before form, and accessing them for testing and cleanup purposes
in "test" and "after", respectively, ie:

(context create-user "with valid attributes"
  (before ($assoc! :user (create-user))
  (after (clean-database!))

 ... other tests ...

  (test "is valid"
    (is (valid? ($get :user)))))

Somtimes you write small functions that only do one thing in one context.
These functions do not really need a a context form. Since "test" can take a
symbol and a string optionally, it can accomodate for such cases. If there
are different contexts, but no heavy set up is necessary (something you'd
put in "before"), you may also want to avoid the nesting.

For example, you might want to write a test like this:(
(ns clojure.core)

(test + "without arguments returns 0"
  (is (= 0 (+))))

(test + "with a single argument returns the argument"
  (is (= 3 (+ 3))))

 (test + "with multiple arguments returns their sum"
    (is (= 7 (+ 4 2 1))))

However, you could also avoid some repetition if you like by using context:

(context +
  (test "without arguments returns 0"
    (is (= 0 (+))))

   (test "with a single argument returns the argument"
     (is (= 3 (+ 3))))

   (test "with multiple arguments returns their sum"
      (is (= 7 (+ 4 2 1)))))

The last things in gist I posted are share-test and use-test. This is
basically just for storing functions with (share-test "my string" [arg] ...
stuff ...) and calling them with (use-test "my  string" :some-arg). Because
context, test, before, and after are anamorphic, you could put a "before"
form in their and just share that (without putting it in a "context" first.)
This lets you achieve similar functionality to what is referred to as
"mixins" in some languages such as Ruby.

Whew, lots of stuff there. I've been going through lots of variations of how
I think this kind of context-based testing would work best while retaining
the simplicity and flexibility of just using "deftest/test" and this is
where I'm at so far. Let me know what you think. I've basically written the
above example in specjure before, so porting it to test-is wouldn't be a
problem.

Cheers, and thanks for the read!

P.S. I just typed this stuff without a text editor so forgive any unmatched
parens, etc

-- 
Respectfully,
Larry Diehl
www.larrytheliquid.com

--~--~---------~--~----~------------~-------~--~----~
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
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