What’s the difference between good and bad unit tests? 🧐
Bad unit tests feel right in the beginning. They make you feel confident, because hey, you write tests.
But nothing really changes… Once you get into the refactoring part, and you have to refactor tests as well. And their assertions for the result. This leads to even longer development, even though you invested time in tests at the begging of the project. So this leads us to the conclusion that… not are tests are equally valuable. Some of them raise false alarms. Some unit tests don’t help you catch regression errors. Bad unit tests may be as well slow and are difficult to maintain.
Don’t write unit tests just for sake of writing unit tests! There are 3 things I always keep in mind when I am designing my unit tests.
Focus on the behavior, not on the implementation
When writing unit tests, especially testing the domain model, I avoid mocking anything. The reason is the fact that I want to focus on the behavior, on the outcome of the method. My goal is not to check how this is achieved. Because of this, I am able to change the implementation at any time, without worrying about rewriting the entire test.
Test your tests
This year, I first heard about mutation testing. It was a wow moment for me. Now, whenever I write unit tests, I check myself with the mutant. I check whether I have covered all the cases (and hence, killed all the mutants 👽). Using that tool is like using Grammarly, for example. First, you get a lot of corrections. But as you go, the number decreases, because you learn by doing. The same story goes with the mutant. Check your tests once you’re done to make sure you covered all the cases. You can read more about mutation testing here.
Don’t generalize early on
I found out that generalizing things too early (over-following the DRY principle) may actually slow you down in the long run. By generalizing I mean making (soon to be over-parametrized) seeding methods for the arrange part. Or too many hierarchical classes, just to avoid writing something twice. I always start simple, allow some duplication, and see where this takes me. I find myself refactoring the code often, especially when I work with a new domain. I found out that introducing generalization too early in tests makes it harder. Later, when it feels a little bit too messy, I really like introducing a builder pattern for seeding the data.
What are the rules that you follow when testing?