The Five Stages of Testing (And How I Finally Reached Acceptance)
Background
I’ve been on quite a journey with testing, When I first started they were the part of the codebase constantly breaking and causing endless regeneration. Luckily, that was some time ago and my views have matured, mostly through being burned by not building tests and seeing the difficulty of replicating a bug.
My growth of testing(in the view of stages of grief)
- Denial “100% code coverage is unrealistic”
- Anger “why are these mocks suddenly failing?”
- Bargaining “maybe some tests are useful for validation”
- Depression “this unit is too large for me to test”
- Acceptance “uncle bob was right all along”
now I enjoy upgrading and building and upgrading code coverage. not just because it’s a kpi but because it makes my life easier in the future if there is a regression I will be able to more easily replicate the issue. I sleep better knowing that code can follow failure paths and recover not just follow happy paths.
Testing guild
moreover recently at muzz I have taken the responsibility of creating and running a testing guild which has made me think about what makes a good test and how can we make tests easy and a joy to write. The first thing I did was build a tool to break down the coverage by teams and by service so we can better understand the situation. I did this by using codeowners which is a list of files which can be owned by various sub teams.
What makes a bad test
- Difficult to write eg lots of mocks
- Slow to setup the universe almost needs to be simulated to run the mock.
- Flaky eg they fail because a container has failed to start or there is a degree of randomness somewhere in the test. I once had to fix a test because it did not account for daylight saving time.
What makes a good test?
Of course it would be easy to say FIRST principles which are:
- Fast
- Independent
- Repeatable
- Self-validating
- Thorough
In a perfect world tests would be a form of documentation indeed sometimes I will add ticket id to test names to show me fixing a bug.
- Readability not only that they are readable but it’s clear what the purpose is of them, eg is this test validating a bug has been fixed or is it validating a new feature.
- Manageable/maintainable, tests should be easy and fun to write. if they are not people will not write them and see them as a chore not a help. additional tests should be easy for a new developer to understand and modify.
The role of the linter
linters are not just useful for code style but they are useful for making sure units do not get too large and unwieldy. they can also help enforce best practices. Make sure to set a function or statement limit to ensure units don’t get too large. this is something which helps across teams as well as individuals.
Examples
to me this is the starting points. for me now I setup a lot of tests for example. if I had to simulate a users social network feed. i would build a function like this.
go code snippet start
type FeedParameters struct {
numberOfPosts int
postModifiers []func(p Post)
}
func setupFeed(t *testing.T, params FeedParameters) {
// setup feed
}
func TestBasicFeed(t *testing.T) {
params := FeedParameters{numberOfPosts: 10}
setupFeed(t, params)
// paginate over feed
}go code snippet end
this allows for this function to be re-used and yes be dry. but more importantly it makes the tests clean. nobody wants to read 100 lines of setup just to see one assert. Moreover this is more failure prone as not enough asserts could cause regressions. this allows for tests to be written like this.
go code snippet start
func basicFeedTest(t *testing.t){
params := feedParamters{numerOfPosts: 10}
setupFeed(t, params)
// paginate over feed
}go code snippet end
this test is clean and easy to read. it also allows for advanced tests by passing in the functions to modify the posts.
one thing I would add for this is make sure you have tests for the BasicFeedTest or you could get yourself into XKCD or even worse Nebraska.
Further reading
Conclusion
If you are not writing tests because they are a chore, it might be worth taking a closer look at how you are writing tests or what you are testing. Are your units too large? Can you break down your function and test those? Tests should be an aid and a joy.
And with that, I wish you a happy, bug-free future!