Thursday 7 October 2010

TDD: Make your intentions clear(er)

A habit I've got into recently is creating more and more classes specifically for the purpose of testing.

If the class extends a class which is meaningful to my application it's called ThingSupport (where Thing is the name of the Class it extends).

If the class is extending a more generic class - eg Sprite, or Command with an empty execute - then it's called SampleThing. (Again, Thing is replaced with the name of the Class it extends).

So - my source library now contains a growing number of classes like:

SampleCommandA
SampleCommandB
SampleVO
SampleEvent

UserVOSupport
MainMenuItemSupport
AccountModelSupport

The Support classes tend to populate the constructor parameters.

So if the constructor of UserVO is

public function UserVO(key:uint, username:String, password:String, jobTitle:String etc )

then the constructor of UserVOSupport is

public function UserVOSupport(key:uint = 1)
{
super(key, 'username_'+String(key), 'password_'+String(key) etc)
}

Now I can create a dozen UserVOs with distinct properties only passing an incrementing uint variable to the constructors. Lovely ... but...

...it's not about typing less

The ThingSupport classes do two things: they make it easier for me to instantiate the class, but also they isolate my tests from changes to the constructor of the Thing classes. If I update the Thing constructor to include another param, or change the order, I don't have to touch my tests - I just make a matching change in one place - ThingSupport.

When my coder-friends protest that they find TDD tiresome because if you make changes you have to do loads of maintenance on your tests, this makes me think that they're not isolating their tests well enough.


So what about the Sample classes?

What's the point in creating a SampleEvent? If the class/test requires a particular class why bother to create a sample class as well? Why not just use Event?

This is the newest thing I've added to my workflow - and hey, by the end of next week I might have changed my mind - but I'm experimenting with using it to make my intentions clearer in my tests.

As in - what's *real* and what's just test scaffolding. Particularly in integration tests where multiple classes are working together. The use of Sample as a prefix is helping me keep my head straight on which elements are being tested and which are simply provided to wire things up.

That way, when I look through my code and it uses SampleCommandA I know that the SampleCommandA is not part of what's being tested - it's just fodder for the test.


Couldn't you just use mocking?

I do use mocking (with mockolate) a lot. Actually I tend to use stubbing / verification - via mockolate. I use it to mock interfaces for services so that I can verify that a method on that service has been run by a command with the correct treatment of the event payload, even before I've implemented the concrete service to the interface.

I also mock (for verification) framework elements - injector, CommandMap - to verify that runtime mappings have been made correctly - again, usually in Commands. It's easy to think of Commands as being so banal that they're barely worth testing, but a Command that doesn't actually run the method on the service, or doesn't manipulate the event payload correctly, or fails to make a mapping could potentially lead to hours of debugging once compiled into the wider app.


Focus on minimising your WTFs per minute

As @mark_star tweeted this week: "The only valid measurement of code quality is WTFs/minute" (clean code).

I believe the use of Support and Sample classes is significantly lowering my WTF rate. It feels good.

I'm starting to think that a focus on "less typing = better" is really dangerous in a coder. I can type at 80 wpm. Can I code at 80 wpm? Can I f***! This 'more code = slower' belief is part of what keeps people from embracing TDD.

But it's a dangerous obsession for more than one reason. The search for the perfect IDE may actually lead to an increase in the kinds of WTFs that go on for hours and even days.

@_ondina posted some wisdom on the robotlegs board today about the value of being intimate enough with your code that you can write it by hand. We were discussing code generation tools for robotlegs at the time - and she made the valid point that if you rely too much on code generation tools then there's a chance that small errors - [inject] vs [Inject] - creep in to your code and you have no way of diagnosing them, because you haven't built up enough experiences of what looks right for your brain's pattern recognition system to let you know what is wrong.

Food for thought.


No comments: