A while ago, I watched The Magic Tricks of Testing by Sandi Metz and had an epiphany. This was the perfect approach at testing because it answered perfectly the question I always have:
What should I test exactly?
This blog note are my notes about that talk, for future reference. It only deals with unit tests, not integration test. You can check the slides on Speaker Deck.
Goals of unit tests
They must be:
- Thorough: must prove completely that the single object under test is behaving correctly
- Stable: must not break with an implementation detail change
- Fast: minimize development feedback loop
- Few: the tests must be the most parsimonious proofs
Focus on messages
Messages are from three origins for an object under test: Incoming, Sent to Self and Outgoing. Those messages come in two flavors:
- Query: Return something and Change nothing (no side effect)
- Command: Return nothing and Change something
Rules of testing
- Test incoming query messages by making assertions about what they send back. Test only the interface, not the implementation (do not look inside the capsule)
- Test incoming command messages by making assertions about direct public side effects.
- Do not test private methods, do not make assertions about their result, do not expect to send them
- Do not test outgoing query messages
- Expect to send outgoing command messages
Rule 3 can be broken if you deal with a complex private algorithm that you need to TDD. The tests have value early on, but as time passes they prevent people from refactoring/improving your code. So just delete them.
Rule 5 can be broken if side effects are stable and cheap (close, not far away).
About mocks
A mock is a fake of the real object, and it’s the developer’s job to make sure there is no API drift, to ensure test doubles (mocks) stay in sync with the API.
Test frameworks can help you do that. For instance, with rspec-mocks
, you have:
expect(Person).to receive(:find).and_call_original
Person.find # => executes the original find method and returns the result
Source: Readme.
Here is a small experiment I setup to test rspec-mocks
and
rspec-fire
way of preventing
stubbing nonexistant methods (API drift). You can see that rspec-fire
is more explicit
at telling you that you stubbed an nonexistant method than rspec-mock
.