On writing tests
I have been asked several times about my software testing practices so I decided to spend some time and write them down for future reference and feedback.
Let's get into it
My tests
How do they look like?
In structure, something like this:
it 'some test' do
setup
exercise
verify
teardown
end
Characteristics
- Verbose
- Do not use shared global state
- Do have these 4 clear steps: Setup, Exercise, Verify & Teardown
- All the 4 test steps are co-located
And here's why
Before going into details let's first analyze how are the modfication patterns for test files. In my experience it's very common that once the tests are written, it's very likely that future modifications are made in very few specific places in a file, isn't it better if you don't have to read or refactor unrelated tests or setups in a file just to do your change? Do you agree?
That's why:
- I like
verbosity
in tests because tests are documentation, the more context the better for the reader. - I don't like using
shared state
in tests because they favor mutability and coupling, instead I prefer to isolate the contexts for every case which makes the code easier to change. Yes, I'm looking at you sharedbefore
andbeforeEach
and friends. - I like to make each
test step
as clear and semantic as possible so there is a clear distinction between the phases, for the same purpose as before: readability. - I like to have all the 4
test steps
co-located
, yeah, this helps by providing better context for every test in large and complex scenarios.
Across the years I have realized that it is very valuable to write tests this way, where everything is there put together like a paragraph, no magic tricks, no need to analyzing shared globals or scrolling up and down looking for more context just to do your thing. How many times have you been bitten by a chain of before
or beforeEach
with overlapping declarations or with subject
and let
crazy combos? (some Rubyists will understand)... Now you get my point, I suppose... Another benefit is that while keeping things simple for tests if it starts hurting, it's most likely because you may have design problems in your application code.
What about mocks & stubs?
These are my "rules" for mocking and stubbing (in unit tests):
- Don't
mock
what you don't own Stub
query messages performed by the unit under testMock
command messages performed by the unit under test
The last two are self-explanatory but I will care to explain with a simple phrase. You should only care about the query responses
and the command side effects
, nothing more.
Parting thoughts
As a Ruby programmer who values a lot of principles like DRY
it has been a long way to get to this point, but then I realized that it is OK to write tests using a different approach because aren't tests a different kind of code? These practices intend is to optimize for readability and maintainability and if you ask me if this the right thing to optimize or if this gets the ROI we should be expecting from tests - As for today, Oct 2, 2019
my answer is a resounding YES
.
What do you think? how do you write your tests? what practices do you use and why?
Looking forward to hearing your feedback
Till the next time :}