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
How do they look like?
In structure, something like this:
it 'some test' do
- 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?
- I like
verbosityin tests because tests are documentation, the more context the better for the reader.
- I don't like using
shared statein 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 shared
- I like to make each
test stepas 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
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
beforeEach with overlapping declarations or with
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):
mockwhat you don't own
Stubquery messages performed by the unit under test
Mockcommand 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.
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
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 :}