Sandi Metz gave a talk titled The Magic Tricks of Testing at Rails Conf 2013. She tells us about what should be tested and whose responsibility it is to do so. Here are my takeaways.
We want a unit test to be:
Focus on messages
There are three types of messages:
- incoming (receives from others)
- sent to self (the object sends to itself)
- outgoing (sends messages)
These messages come in two flavours: they can be queries or commands.
A query message returns something and changes nothing.
A command returns nothing and changes something.
The problem is, we conflate commands and queries. (e.g. pop method). It is not evil per se to mix command and query methods, but they get tested differently.
Incoming query messages
class Wheel attr_reader :rim, :tire def initialize(rim, tire) # .. end def diameter rim + (tire * 2) end end
The diameter method is an incoming query. This is how it is tested:
class WheelTest < MiniTest::Unit::TestCase def test_calculates_diameter wheel = Wheel.new(26, wheel.diameter, 0.01) end end
Rule: Test incoming query messages by making assertions about what they send back (i.e. their state).
We don't care about how it's done. We care only about the message that is sent back. We test only the interface, not the implementation. That means we can change the implementation without breaking the tests.
Incoming command messages
class Gear attr_reader :chainring, :cog, :wheel def initialize(args) # ... end def set_cog @cog = new_cog end end
set_cog method is a combination of a query and a command.
It returns a value - that's the query part - that gets set on the
@cog instance variable - that's the command part.
We test only the command:
class GearTest < MiniTest::Unit::TestCase def test_set_cog gear = Gear.new gear.set_cog(27) assert(27, gear.cog) end end
Rule: Test incoming command messages by making assertions about direct public side effects.
The receiver of the incoming message has sole responsibility making assertions about its value.
Rule: Do not test private methods. Do not make assertions about their result. Do not expect to send them.
Writing tests for private methods binds us to a specific implementation and make it impossible to refactor without breaking the tests.
It's an overspecification that adds costs, and absolutely no value.
Outgoing query message
Rule: Do not test outgoing query messages.
Do not make assertions about their result. Do not expect to send them.
If a sent message has no visible side-effect, then the sender should not test it.
Outgoing command message
Test outgoing command messages by setting expectations on them, expecting that you send them.
- Be a minimalist
- Use good judgement
- Test everything once
- Test the interface
- Trust collaborators
- Insist on simplicity