Most books on TDD talk about unit tests.
But what is this mysterious entity, the unit test?
unit tests are low-level, focusing on a small part of the software system
The idea is you break your system into “units of work” and then test them to make sure that they do what they should.
With full-stack tests, our unit of work is the entire feature – at the very least, the happy path through that feature.
With unit tests, what is our unit of work?
In an Object Orientated system (and I have much to say about Object Orientated systems), the obvious unit of work is the Class.
And in a Rails app, partly because of some design decisions made by DHH over ten years ago, the unit tests tend to mean “things that test the contents of your app/models folder”.
This has then lead to a big argument between O-O purists and pragmatists about whether testing an ActiveRecord model is truly testing a “unit” – the pragmatists say “yes it is, the model represents the behaviour of your app when in a given state, where that state is stored and retrieved from the database” and the purists say “persistence is orthogonal (a fancy term for different) to what the object does, each object should be responsible for one thing and one thing only and anyway, using a database slows me down”.
I don’t give a crap.
What bothers me is if you test every model in the database, your test suite gets slow. And when your test suite gets slow, you get tempted to leave stuff out. And then you find that suddenly your tests aren’t really worth running as they don’t cover the important stuff. So you leave more out. And eventually you
rm -rf test and head gently slumps to the desk.
So I’m with the pragmatists in that there is importance in the behaviour of your application that can’t be completely isolated, I’m also with the purists that behaviour and persistence are different things and speed in your tests matters.
Which is why I do things differently.
Firstly, credit where it’s due. I was thinking about how to do this as a project I was working on struggled under the weight of its own tests (and yes, I
rm -rf`ed the test folder more than once). I hit upon a great way forward and started playing around with it. And then I saw this video by Justin Searls. It was the absolute perfect way to describe what I was trying to do – it took my structures and moved them on to another level. Thank you Justin. I owe you for this.
So at its heart, we have the behaviour of the app, and how it stores and retrieves its state. In a Rails application, both of these are represented in ActiveRecord models. Sometimes behaviour is represented in controllers, but that’s generally frowned up as it’s harder to reuse that behaviour in different places.
But testing the models is slow. And prone to knock-on effects – if I’m testing the Customer class but I pass an invalid detail to its associated Invoice class, the test may fail, even though it’s not a fault in the Customer. (It’s not really a big deal but the purists would be barfing right now).
What really matters is what the application does .
And, in grand computer science fashion, if something is hard to do, you add another layer. (Again, the purists are barfing as really, if you want to do proper O-O then you need to add about six layers to Rails – well I never liked purity, mucky is much better and this does it quite nicely for me thank you).
So your request flies in to your web-server, makes it to your router, fires into a controller action and the controller picks some models, tells them to do things and then sends the response back via a view – right?
No – the request hits the controller and the controller picks a command to execute, telling it the parameters it has received and any other pertinent information (such as who the current user is). The command then tells the relevant models to do things, which then involves the models loading information to and from the database as required.
In other words, the router routes, the controller packages, the command spews out orders and the models read and write from the database. Your models no longer parcel up both behaviour and persistence – the behaviour is moved to the command layer and the persistence sits in the models.
We’ll get to how this works next time … or sign up below for weekly updates – and get our free Ruby on Rails security guide.