Unit tests - what are they and why don't you write any?
5 min read
OK, the title was supposed to get you riled up - if you are the kind of developer who indeed does write unit tests.
However in a recent poll* I conducted on my Twitter account, 50% of people admitted to having NEVER written a unit test, or at least not having written one in a considerably long time. 🤯
*not a credible study in any way but the only data I have to go by.
There was a range of answers varying from "My employer doesn't give me enough time" to "I don't know how to write them" to "They're useless anyway, I'd rather a QA engineer tested my code".
Interestingly, a few people said that the reason they didn't use unit tests is that tutorials are too basic, and they understand how to create a test for something small but when it comes to a real-life component with multiple dependencies and processes, they couldn't apply it.
I then did another poll and that answer won, if only by a small amount.
So that's something I will hopefully write a blog post about, something which gives more in-depth tutelage on some real life examples and how to write useful unit tests for them. Watch this space! 👀
Until then, here's some information on unit tests and a bit about why I think they're worth doing as part of your workflow.
What is a unit test?
- A unit test is the smallest test you can possibly do on your code.
- The perfect unit test class will only test ONE class.
- Each test will ideally make ONE assertion - although I personally don't mind a few to avoid lots of tiny tests.
- Unit tests are FAST, hundreds can be run in less than a few seconds. ⚡
- A good way to write unit tests is to use the Arrange-Act-Assert pattern.
- Arrange: This is where you set up any state for your test. You might mock dependencies here or set up some objects/classes.
- Act: This is where you call your function or method which you are testing.
- Assert: This is where you verify the outcome is what you expected.
- You should avoid testing implementation detail and instead focus on output from your class methods. This keeps tests from being brittle if you changed something internally, e.g. you decide to store some objects in a list instead of an array.
- The point of unit tests is to protect YOU (the developer) from breaking your own code. You're not going to be testing anything external to your class here. You won't be able to tell if your API integration works with the 3rd party API. But you CAN check things such as, what happens when I pass in a null parameter here? Do I expect an exception? What type of exception is it?
- Unit tests also serve as code documentation. They help others who are new to the codebase understand how a class should be used, and what behaviour to expect!
- Having robust unit tests mean that when you come to refactor code, you can start with green tests ✅, make your changes, and if any tests now fail ❌, you know you've made a mistake somewhere! Refactor with confidence.
- Running tests as part of a build pipeline means every time you build your code, it's being tested - and helps you avoid silly mistakes.
- Most unit test frameworks have built-in support to allow you to do things such as write one test, but pass in multiple sets of different parameters to run multiple scenarios with that one test.
- Writing code with unit testing in mind means you're more likely to follow good design practises.
- For example, you may be more likely to use Dependency Injection (DI). This is very useful for writing unit tests as it allows you to mock the dependencies you inject in. You'll find that you can't test classes at all if you are newing up concrete classes within your class as you can't mock those classes. (DI is awesome!)
- You may also find you are putting more values into configuration rather than hard-coding into your program.
So you might still be thinking that it's a waste of time, and you have a QA who tests your code. Catching bugs earlier on in the development process will always be the cheapest option. If code moves from development to QA, and then back again because QA found an issue which could have been caught by some easy unit tests, then it's adding on potential days of development which as we all know, are 💲💲💲.
Or you might be thinking that you're only working on your own personal projects, and there's no need to write tests because you're the only person who needs to work on it. I would suggest using this opportunity to practise writing unit tests. Especially if you are starting out in your career and you're interested in joining a company as a software developer - you'll often hear employers ask about TDD or job roles stating that they use TDD.
What does TDD even mean?
TDD, or Test Driven Development, means you write your first unit test before you write any other code. That's right! You START with a test. This test will obviously not compile initially, so as you write the test you write the first parts of your class until it can compile.
The main thing is, before you implement your code, you run the test and ensure it fails ❌. This ensures no false positives!
After this, you implement your functionality until the test passes. At this point, you stop and refactor your tests if necessary. Then, you write the next one.
The steps are:
- Write a failing test ❌
- Make the test pass ✔
- Refactor 🖌
- Repeat 🔁
Refactoring is an important step! Unit tests are a part of your codebase like everything else, and so best practises should still be adhered to.
It's a very iterative process and admittedly I've only used true TDD a handful of times. Some companies religiously use it though, so it's worth learning what it is and how to do it.
I could go on for hours about unit tests, but I'm not going to, because I doubt anyone would read to the end.
However I hope this has been of some interest and value to you!
If you enjoyed this blog post, please subscribe, and follow me on Twitter at KatyCodesStuff