Quick and Effective - Per test databases with CouchDB
Testing is surely the aspect of software development that I find most irksome. If the goal is an efficacious, fast-running test suite, it’s not an easy target and as a result, I’m almost never happy with my tests.
A few years ago, tired of a ‘unit test’ suite that took twenty minutes to run, and a functional test suite that took almost five times as long, I jumped on the mock bandwagon as it went by. I’ve since jumped off. Mocks have their uses, but they don’t typically stand the acid test of tests - “Broken interface means failing tests, Working interface means passing tests”. Almost axiomatic, but often ignored.
Which brings me onto model level tests and CouchDB. I’m only going to have test confidence when my models are operating on data retrieved from the database. That confidence comes at a price - my test suite is slow. But as I mentioned before, working with CouchDB encourages new ways of thinking.
Traditionally, tests that interact with a database clear it out before every test case, load it up with the required data and finally run the actual test. But a single CouchDB instance supports many databases, potentially many many databases. What if I had a database per test, pre-loaded with the required data? Tests would still issue GET requests, but the costly setup stage would be obviated. As it turns out, this is fairly straighforward to do. The basic premise is as follows:
- Specify the test setup code in a lambda (or any delayed execution construct)
- When the test starts, query CouchDB for a known document id against a database whose name matches the current test
- If the known document exists and if it contains a property whose contents match the test setup code exactly, you’re done. Just run the test.
- If the condition above doesn’t hold, the test setup code has changed. Delete the database named by the test, create it again, run the test setup code against it and store a document containing the test setup code. Now run the test.
The following is extracted from a real spec. The code inside the cdb block is executed if and only if it hasn’t already been run.
it "should know one another" do
cdb do
p1, p2 = Player.stock(:name => "p1"), Player.stock(:name => "p2")
p1.acquaint p2
end
# p1 and p2 are methods that load players by names p1 and p2
p1.should know(p2)
p2.should know(p1)
end
It’s worth pointing out that you’ll want to rerun the lambda if the object creation code changes, even if the lambda itself is unchanged. This is done easily with a command line switch.
I’ve published the code I use for doing this with RSpec on github. The speed up is of course test dependent, but the specs that I’ve applied it to run almost an order of magnitude faster. Happy days!