On testing

Sunday 6 October 2013

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:

  1. Thorough: must prove completely that the single object under test is behaving correctly
  2. Stable: must not break with an implementation detail change
  3. Fast: minimize development feedback loop
  4. 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:

  1. Query: Return something and Change nothing (no side effect)
  2. Command: Return nothing and Change something
Object under test Diagram
Object under test diagram, by Sandi Metz
Unit testing minimalist grid
Unit Testing Minimalist Grid, by Sandi Metz

Rules of testing

  1. 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)
  2. Test incoming command messages by making assertions about direct public side effects.
  3. Do not test private methods, do not make assertions about their result, do not expect to send them
  4. Do not test outgoing query messages
  5. 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.

Would you like to learn programming? I am CTO of Le Wagon, a 9-week full-stack web development bootcamp for entrepreneurs, and would be happy to have you on board!

comments powered by Disqus