Time is the most scarce resource in the world and it should not be spent too much on writing tests (& maintaining tests) while we are capable of doing more important and better things.
Tests are a considerable part of every coding project and roughly speaking we spend more time coding the tests than actually coding the main project.
Lets save time!
How can we save our time and our fellow developer’s time ? By writing better tests.
Test name
Trust me, the test name is the most important part of any test file.
Just by reading the test names we should be able to know :
- What is being tested : a particular method, a particular scenario or a service
- What are the outcomes : example – test should fail with exception, api call successful, file created successfully, etc
- What is causing this outcome : example – invalid input, negative number present in file, etc
Even a programmer who is not familiar with the programming language in which the test is written should also be able to understand a skeleton of the test file just by reading the test names.
Variable name and Comments
Use good variable names that are self-explanatory and clear. However be cautious while naming that it doesn’t become too long or over explanatory.
Prefer to add the comments in the test, in case the flow gets confusing. Avoid comments that are too obvious.
Order of tests
The tests in the file should be in a logical order, they should be grouped (& sub-grouped). It helps the reader quickly understand the flow of the test file. Top priority in the grouping should be given to the behavior being tested, i.e tests testing the same behavior (e.g a method F()) should be grouped together. Also the success cases or common behaviors or primary functionality should be occurring first in the order.
Some languages like dart have inbuilt support for the “group” , it should be leveraged well.
Take care of post test execution (i.e @After, tearDown())
Make sure not to create order dependent tests. A test should be completely isolated in itself. I have encountered this issue once where I was adding a new test and it was giving unexpected behavior, and after debugging it for hours I realized some other tests are affecting its outcomes because the mocks and the test database are not cleaned up after every test.
A test should clean up every setup it is using, unless it is not a common global setup needed by every test.
Avoid too much modularity – a bit of duplicity is fine
Too much modularity can affect readability in tests. Be cautious while you extract out things in the common methods, If it is not making it more readable it is better to have a bit of duplicacy.
Tests files which become quite large generally have these issues, developers need to jump a lot in multiple common methods to understand the context.
Leave things better than you found them
While you are working on an existing test and spot something which can be improved, please do something about it. Put a TODO, inform the owner about it or if it is a small effort please refactor it yourselves.
(This applies for everything, not just test)
Avoid creating a broken window at all cost
This one is my favorite.
From wikipedia – “Social psychologists and police officers tend to agree that if a window in a building is broken and is left unrepaired, all the rest of the windows will soon be broken. This is as true in nice neighborhoods as in rundown ones. Window-breaking does not necessarily occur on a large scale because some areas are inhabited by determined window-breakers whereas others are populated by window-lovers; rather, one un-repaired broken window is a signal that no one cares, and so breaking more windows costs nothing.”
Simple way to understand it – Visible signs of dysfunction creates more dysfunction. Example – A street which is already quite dirty and full of garbage will be made more dirty over time unless someone takes proactive action to clean it all up, whereas if a street is well cleaned we will hesitate to make it dirty.
Same goes for code.
An unattended issue in the code tends to create more problems in future (more TODOs, more hacky fixes, missing tests, etc). Things are easy to fix while they are small.
Whenever you add a new change, create tests at the same time. It is highly time consuming for someone else to later add the tests, and it is a classic case of broken window theory, eventually the test file will be missing because for any new contributor it is quite a lot of effort to create the test file and add their tests.
Avoid over-complicating test by testing everything
One should aim to test the behaviors in the test rather than implementation. Prefer testing the possible scenarios (LOC coverage) that can happen rather than testing all the possible code paths.
Testing all the possible code paths & combinations is good in terms of robustness of test, but it could be quite time consuming for further iterations. With smaller tests generally this issue is not evident but for larger tests readability and maintainability drop down drastically if tests are testing too many possible combinations instead of behaviors.
Leave a comment