What makes a test good? — Testing #1
Last time I wrote a bit about the importance of testing your code. This time I’ll focus more on what test has to have.
Ingredients of a good test
Writing a test is just a small part of a success. It’s great if you do it, but in the end, this test has to live along your codebase as its integral part. To live, it needs to evolve with your code, it can’t be a slab of concrete that holds your code in place and makes your life miserable when you want to refactor some code.
To make it live, you have to treat it well, and in my opinion, one way to do it, is by making it:
- Test one path
- Easy to understand
Readable and concise
Readability starts with naming. Name your tests with their intention. Do not call them testOne(), testMethodName(), test1(), testCase1(). This gives no information about the test. Name them with the intended result of an action, that way, when a test fails, and you see its red name in the report, you will know exactly what part of your code broke, without the need of looking at the code.
For example, I prefer to name my tests with a word should at the beginning. This gives you a lot of options for describing the intention.
Do not be afraid of using long method names, you have all the characters in the world (well, at least until the class file size limit).
So we named our test well, now let’s talk about contents. My rule of thumb is to make the test fit your screen. And by this, I mean normal sized, Full HD screen, with IntelliJ maximized and its side and bottom panels visible. so that is not that much space.
My preferred way to address this is to use the BDD approach to tests, which boils down to dividing your test logic into few sections, and make them as small as possible:
- given — 1–3 lines, preparing a state of a system and input data
- when — 1 line, performing a tested action
- then — 1–3 lines, checking of the results of action
Test one path
Making your test contain only one path makes things easier later. Imagine that your test fails, its named shouldCalculatePayoutForClient(). Ok, so the calculation of payout for a client does not work, good to know, but what does not work? Maybe it is some special kind of client, maybe he has some special state, it’s suspended, it’s VIP, has some special product? There are many questions here and many, many possibilities.
It would give you much more information if it would test one concrete example, shouldCalculatePayoutForVIPClientWithOneProduct(). Now, this pinpoints a problem better. Of course, next to this test, you should have additional tests like: shouldCalculatePayoutForVIPClientWithMultipleProducts(), shouldCalculatePayoutForNormalClientWithOneProduct() etc.
On top of that, remember, you have one line in your when section, so use it wisely ;)
Making tests as small as possible should be your priority. On top of that, when these tests are well structured and well named you will have a documentation created as a side effect. Remember, you and your fellow developers will read those tests. They will read them when they debug, refactor and develop your application. You are not making tests to produce target code coverage metric, no, you should do it to make your life easier.
In the next part, I’ll focus more on how to make your tests easy to understand.
Originally published at DEVelopments.