Categories
Agile Ruby Software

Testing trivialities … and double-entry bookkeeping

From time to time I end up in a discussion (as often as not with myself) about the point at which something is so trivial that it doesn’t justify creating a unit test (or behaviour spec, in more BDD-like language).

The latest incarnation of this question arose while starting work on a Ruby on Rails app1. Rails has what I like2 to call this duck configuration (if it walks like configuration and quacks like configuration…). It looks like config, but it’s implemented as method calls. Validations are one example:

class User < ActiveRecord::Base
  validates_presence_of :username, :message => 'is required'
  ...
end

Here’s the spec (following the style demonstrated by Luke Redpath) that caused that line to be written:

context "A user" do
  setup do
    @user = User.new
  end

  specify "should be invalid without a username" do
    @user.attributes = valid_user_attributes.except(:username)
    @user.should_not_be_valid
    @user.errors.on(:username).should == "is required"
  end
  ...
end

Aside: the example above doesn’t follow One Expectation per Example – should the error message be in a separate spec? I’m inclined to think that would be overkill, but I’d like to hear your opinion.

Eagle-eyed readers might have noticed that the spec is significantly longer than the code itself. It’s only configuration, after all – does it really need a spec? I’m inclined to say yes.

Firstly, there’s a slippery slope here. If this line of code is too trivial to write using TDD/BDD, maybe so’s that small helper method, or that exception handler. Before you know it, you’re drifting back into the bad old ways of writing the code first, then adding tests afterwards when you think it’s necessary.

The other reason is that test-driven development is a kind of double-entry bookkeeping. Every piece of behaviour you create has a test/spec in one ledger, and an implementation in another, and every time you run your test suite, you’re balancing your books. If the required behaviour changes later, you rewrite the spec and adjust the implementation to match. You probably aren’t going to have auditors looking over your code, but you will have future developers (including yourself, long after you’ve forgotten writing the code), and they’ll appreciate the clarity of intent that full specifications3 provide.

[tags]tdd, bdd, rspec[/tags]


1 Did I mention that we’re rewriting the SDK portal in Rails?
2 Since I invented the phrase while writing this post.
3 Executable specs, obviously. A full set of specification documents rarely provide any clarity at all, and often just add to the confusion.

5 replies on “Testing trivialities … and double-entry bookkeeping”

After testing a few different approaches, I’ve settled on using One Expectation per Example.

What I do is use the setup block to set up the state of whatever I’m testing, and my specify blocks to confirm the state is as expected. I aim not to change the state in any way in the specify blocks, and also try to keep my specify blocks to a single line of code.

So in the example above I’d assign to @user.attributes inside the setup block, and then I’d have two specify blocks: one to check to make sure its not valid, and one to test the error message.

Actually I’d probably have one more specify block just to make sure the number of error messages is equal to 1, which alerts me if other errors creep in unexpectedly.

It probably goes without saying that before all this I’d probably have another context block that assigns all the valid_user_attributes and specifies that @user.should_be_valid.

Yeah its a bit more verbose than what you’re showing above, but it really is much easier to pinpoint where the problem is when stuff goes wrong. If you’re using Textmate and folding, you can just minimize finished contexts and specifications, so it really doesn’t take up that much visual space on the screen.

Thanks Dan, that makes a lot of sense.

The point about not changing the state in the specify block is interesting – I think this is where a three-level given-when-then approach (à la Dan North) might be better than rspec’s context-specify model.

Leave a Reply