The magic tricks of testing
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:
- thorough
- stable
- fast
- few
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, 0.01)
assert(26.02, wheel.diameter)
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
The 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.
Summary
Underlying principles:
- Be a minimalist
- Use good judgement
- Test everything once
- Test the interface
- Trust collaborators
- Insist on simplicity