Why Write Unit Tests
Discover why unit tests are important in the world of software engineering. Various reasons are presented towards adopting unit tests and how to think of them in your code.
Ever dreaded fixing that bug you pushed to production contemplating whether you could’ve spotted it earlier on. Not always though most of the time you can find it and fix it, well written unit tests can tremendously help you with that. I will not concern you here about well written unit tests but portray the importance of writing unit tests in the first place. Will not write code but will explain conceptually why unit tests make sense to be in your skillset. Unit tests are not the only form of automated testing there is but certainly the most popular. There is great support among all popular programming languages. It doesn’t normally require a lot of resources to run unit tests therefore making them cheap, easy to write and maintain. Let’s start. The first encounter with a unit test will feel like why bother? Why invest the time to test the code you already wrote earlier and believe to be working? Why maintain them? Why run them as part of a CI pipeline? A lot of questions. Code that we write is usually simple if it was just one line. Nothing goes wrong. As the number of lines increases, the complexity increases. Each line becomes a step in the execution. When you have straight steps, it is can be fine but more often than not you will have if and else conditions as well as loops, functions calling functions and dependencies you have no idea how they operate. This is all a lot to keep in mind all at once while also having multiple engineers contributing to the same codebase. Unit tests are essentially code that runs and verifies your code is working. The running part equates to you writing code that will call the code we want to test or better named system under test (SUT). The verification part is the assertions or expectations you will verify based off product/business requirements. That was a mouthful full of jargon. Let me draw you an example. Let’s say you just wrote a function that will compute and tell you which elevator to use once you request to go to the nth floor from the ground floor. You can only request the elevator from the ground floor for now. So our system under test (SUT) is that function let's name it ‘getTargetElevator’. It takes 2 arguments, the first is the current floor and the second is the target floor. We don’t know how this function works but we know once we call it with the appropriate input will get an output. Great. This function now is what we want to test. A simple first test would be to check if I am on the ground floor and request to go to the 7th floor then I will get the expected elevator A as my output. So the unit test would look like: ``` // Running part elevatorName = getTargetElevator(0, 7) // Verification part assert elevatorName === A ``` Sweet. If you don’t get it yet then think of a simpler function. Imagine an addition function and imagine how it would behave. Moving on. That was a very simple unit test. Though we didn’t cover other cases yet, it is astounding to see how you can use code to test code. It unlocks a lot of opportunities. Sites like [HackerRank ](https: //www.hackerrank.com/) are basically utilising it not only for testing their production but also the submissions you submit. I want to highlight the first reason why write unit tests which is to test for behaviour. Here I was given a set of inputs and was asked to evaluate the associated outputs to prove correctness. Thus, a unit test is basically a mini program that evaluates correctness. At any point, I didn’t think how that function was written, I knew it did something. This is the power of unit testing, you at most need to test the behaviour and not how that behaviour was implemented. There is certainly other types of tests where you need to test how fast an implementation is or how much memory it consumes but that is not the everyday unit rest if it is even one. Cool. Take a few seconds to reflect on what we did so far. We made a simple unit test to verify the output. Now if you remember, I mentioned that the elevator can only be called from the ground floor white it is inconvenient I actually want to test that. I want to ask the function to call from the second floor to go to the 8th floor. How do I do that? Well you can write another unit test independent of the first one. In fact you can have as many as you want as long as they make sense to do so. The second unit test covers an edge case that would not be expected to be called from a real production software but bugs do happen and so being safe here thinking about it can help us design software with defensiveness in mind. Now we reach a second reason on why write unit tests. Might be counter intuitive at first but it is repetition. The repetitive writing of unit tests covering differing inputs and outputs protects you against edge cases and allows you to remedy them sooner than later while authoring the code. I usually try to think about potential edge cases whenever am testing and have automated tests cover them. Nonetheless it is a useful thing to have in your toolchain whenever solving any problem. Sweet. Advancing to the next reason why. Luckily we can now extend the functionality of elevator requests to allow for requesting elevators from any floor. How do I protect against extending the code but also preserving the existing functionality. This is where the second power of unit tests shines, the first is automated testing and the second is helping you update tested code while at the same time being able to right away check if you broke anything. So in our case, we can write the new unit test before or after modifying the code. For beginners, I recommend after writing the code, will get more into this later. So let's say we broke the ground floor elevator requests functionality then we can immediately see that it failed in the unit test suite giving us a direct clue. Without automated testing you would have to manually check for all cases each time you update the code just in case something broke, usually there is no documentation as to which cases you need to check so you have to spend time thinking of them. The more complexity there is the more tedious the testing will be and the more error prone you will be testing it. It links back to our beginning paragraphs. Not only it is error prone, you are literally doing a manual job that could most of the time be automated saving you plenty time and stress. Therefore the third reason is safe code changes. You can get creative here and conclude that it is not just for when we want to add new code. Unit testing is very useful for when you want to refactor a function/class/module and so on. It gives you the power to test for backwards compatibility. This is all assuming you had covered all cases initially, if not just make sure to cover extra cases before doing any change. As experience would prove, trivial changes don’t always remain trivial and may cause bugs in production if not tested properly. Amazing. There is plenty more reasons why you may want to write unit tests but will just address one more that I think everyone can benefit from. Usually internal functionality I want to consume doesn’t have fancy docs next to it. When docs don’t exist then I turn into unit tests to try to learn how to use the functionality. This is usually a good saviour of time. Typically, unit tests do a sound job of assessing various cases so you can bet that the unit tests present good documentation. This sub-skill could be useful if you want to understand how open source software works by just reading tests first and implementation second. If no tests exist then good luck. Sometimes I resort to writing unit tests for something I want to use for the sake of learning how it works and how to interface with it. It is not easy and you may not need to do it especially if you’re tight on time but it is a good way to learn new code. So in essence, the fourth reason is self documentation. Actual docs might get stale but unit tests will not because if they get stale then that means they’re failing and if they’re failing then you can’t deploy new code because it is certainly buggy. It is certainly not an alternative for all documentation but for some. Nice. Let’s summarise the four reasons why write unit tests. Those are: - Unit tests test for expected behaviour of your code - Repetitively writing unit tests allows you to test different edge cases - Unit tests provide the peace of mind that existing behaviour still works while making changes or refactoring - Unit tests offer up-to-date "how to use guides" Some things I want to mention before calling it an article. Unit testing can get really boring at times therefore try to optimise how much you test. New features should be unit tested heavily but in reality you will hit some obstacles like how do you test non-deterministic random stuff. I don’t know! You can mock, mocking is a way to have a dummy implementation just for the test but then you introduced a risk of not using the real implementation. Furthermore, since unit tests are usually written to verify business logic. As you test more and more, try writing unit tests before writing an implementation. This way you can ensure you’re just testing behaviour and not fall into the trap of testing implementation. If implementation changes then you risk tests failing or becoming susceptible to environmental changes. You can learn more about that, google test driven development. It is not a silver bullet. To close, referring to the opening paragraph. Whenever you fix a bug in production, try to write a unit test replicating that same bug to ensure that it is fixed and covered by automated tests. As with anything, exercise judgement when writing unit tests, for instance don’t go overboard as you still have features to deliver and bugs to fix.