The Ys of Unit tests - Making sense of a good practice Part 2
Obvious and not so obvious benefits of Unit Testing
Have you ever noticed that bugs have a mysterious attraction to your code? đ¤
It's like they have their own version of a âbug Tinderâ đ!
Swiping right on every line that looks vulnerable đ¤ˇââď¸.
But fear not my fellow developers!
In this article we are going to unveil the secret weapon that bugs dread the most: Unit Tests!
So, let's dive in and unleash the bug-repellent power of our coding arsenal!
In the first part we talked about testing in general as a practice and why we should test. We talked about how everything we write is a pure claim unless tested properly and that in order to move it from the claim column to the fact column it should be tested properly. In this part we will start with the first article about unit testing specifically and why you should write unit tests for your code. We will be talking about the benefits some that obvious for pretty much everyone and some are really not that obvious of Unit Testing.
What are the benefits of Unit tests?
Testing in general is beneficial obviously, integration tests gives us high confidence in the system but writing them is somewhat expensive and getting feedback from them is somewhat slower, end-to-end testing gives us even more confidence in the system interacting with different components or sub-systems but itâs even more expensive to create and maintain. The question that would arise is that: Are there benefits of using Unit Tests specifically? Well I am arguing in this article that the answer is a resounding YES.
But letâs see how it goes, letâs discuss the benefits one by one:
Unit test provides a sort of technical documentation
Surfing through the codebase by reading unit tests
Unit tests done right can actually provide a sort of technical documentation about your codebase. New joiners could easily read through the unit tests you have and understand what a service is doing by just understanding the assertions and reading the tests method names. Actually I have done the same in one of the companies I have worked for, they have written their unit tests so well that I could understand literally everything in the codebase, there was no need (most of the times) to ask questions, because the answers are documented actually in the unit tests themselves. It was so hilarious that I could also makes sense of certain changes and certain history of changes in the codebase by looking at unit tests removed/refactored/added.
The unit tests could provide such valuable assets, thatâs providing technical documentation about your codebase through:
Test Naming: The names of unit tests can often provide insight into what the function or module being tested does. By using clear and descriptive names for the tests, you can get an idea of what the code is intended to do without having to read through the entire codebase.
Test Cases: Unit tests can also provide examples of how to use the code in different scenarios. By creating a suite of test cases that cover different inputs and expected outputs, you can refer to these tests as examples of how the code should behave in specific scenarios.
Test Assertions: The assertions made in unit tests can provide insights into the expected behavior of the code. By analyzing the assertions made in the tests, you can gain a better understanding of what the code is intended to do.
Code Changes: Unit tests can document code changes over time. By reviewing the history of the unit tests, you can see how the code has changed over time and how those changes have impacted the functionality of the code and making sense of them.
Unit tests are powerful tools in the toolbox, not just for verifying things and asserting claims. Not just to act as safety net for you. But also to provide documentation about your codebase. Have you ever imagined that some people out there use unit tests as a mean to make onboarding new people in the team easier? Isnât that great?
Embracing the Change as an inherit nature of Software
There is only one guarantee in software, nah itâs not availability, itâs that they change, a loooot
Itâs almost always the case that software arenât immutable, they change all the times. The change can range anywhere from a simple new feature, a minor requirement change, a teeny-tiny slight misunderstanding of the requirement, domain expansion, infrastructure change, new non-functional requirement, a vulnerability patch, a new update becauseâŚ. because we can why not! đ đđ
all the way to the catastrophic changes such as (Oh wait, we have built a solution for a completely different problem)-kind-of-thing; or some times due to (a cascading catastrophic change in the requirement in a massive enterprise due to a tectonic movements in the sub-domains as a result of what I also call âInitiatives feverâ).
Initiatives fever: Itâs a phenomenon appears in massive enterprises where everybody everywhere all the times want to start all kind of initiatives, the one who start the biggest initiative wins.
A careful reader: OK, so Software changes, now what? What can we set against the fact that software change to reduce the impact of it?
Unit tests are your safety net, the cheapest, quickest, easiest to read easiest to maintain safety net against the fact that Software changes.
Because unit tests verify claims about your software at such a low level, software that change means a change in the claim, and since we wrote our unit tests with verifiability and falsifiability in mind (remember our point above?) unit test would fail and they would grab your attention about the fact that something has changed.
This is unbelievably powerful weapon since you can verify that changes anywhere didnât break something somewhere and that can be easily automated and itâs so cheap and easy to automate that you can run them each time anybody change anything anywhere (e.g. CI/CD pipeline thatâs triggered on feature branches as well).
A careful reader: But unit tests mocks everything else, they are test in a complete isolation. They wonât catch for example integration issues, or the issues that might pops up when multiple units work together.
Yes thatâs absolutely true.
I didnât say we should not use other kind of tests (back to this later) what I am saying or rather trying to make sense here is specifically unit tests.
A careful reader: Forget about faster feedback, forget about cheapness. Is there anything that unit tests can do, that integration tests canât?
I would say YES. Integration tests have a certain scope to them where they verify âbigâ business related flow, they assert thatâs something happened because of a certain interaction. But they cannot for example verify the following âIf another dependency throws an exception that exception should be handled within that unit gracefullyâ. How would you do that in integration tests? For example letâs imagine that you are calling the database and a certain âCommand Exceptionâ for some reason must be handled gracefully âSwallowedâ or not propagated up the calling stack how would you verify that? With unit tests it can be easily done by mocking the dependency and then in the setup stage you setup the method to throw `Command Exception` and you verify in the verification stage that your code is not propagating up that particular exception type. Unit tests basically looks at your code with a such magnifying powers that certain things are only possible with them.
Letâs conclude this point as well:
Software always change, they on 99.9% of cases arenât immutable
What we can set against that fact are Unit Tests
While integration tests are also great tool in the box, that doesnât mean we can use them as substitution or instead of unit tests. To each strengths and weaknesses, pros and cons. And we should strive to use them both
Itâs easier to strive for 100% coverage with unit tests
By achieving 100% coverage with unit tests, itâs easier now to know what kind of integration tests you need, specifically targeting the integrations points that are left unchecked by unit tests, as consequence you become more conscious about what kind of integration tests do you need and thus your testing strategy become more efficient.
Why Unit Testing? Why not other types of tests?
The mess thatâs testing types
There are all kind of test and people are still naming new testing types to this day. So the question now is why not focusing on other kind of tests like for example we could go all the way e2e or integration tests, or 20% e2e and 80% integration tests. Maybe instead of unit testing that usually use mocking (or other kind of ..) we could go for the so called âsociable unit testsâ where instead of mocking (or whatever) the dependencies you actually instantiate the dependencies (to a certain level that they wonât become integration tests) and test the chain in kind of integration testing manner but without the integration part really, just testing services in integration but not for example database calls or 3rd party services calls.
First of all letâs take a look at the famous âTesting Pyramidâ
The upper you go in the âpyramidâ the more expensive the test both to write and to maintain, the less âisolatedâ they are but the âhigherâ the confidence they give you in the system from the business perspective and vice versa. So itâs more expensive for you and your team to go all the way âmanual testingâ and it cannot be automated âhence the manual partâ but as they say âseeing is believingâ itâs high in confidence hence you tested it yourself, right? Integration and e2e testing are less expensive than manual testing and can be automated and they gave you decent amount of confidence in the system but they are very expensive to write and to maintain. The component test are the âsociable unit testsâ.
IMHO, the difference between these layers is the âmagnifying lensâ they use. Unit tests looks at the code with the highest magnifying lens, you are looking at line of codes âpaths if you willâ and you are asserting certain behaviour for each path. But something like E2E testing doesnât have as much âmagnifying powersâ so it looks at the system as âcomponentsâ interacting with each other and it asserts that the result of such interactions have valid response/value from business perspective.
From business perspective, if the user wants to create an order and order must be validated and if valid should be created; from business perspective there can never be such a thing as ânullâ order right? Here where the âmagnifying powersâ of unit test shines.
For example, especially if the e2e tests are âderivedâ by legitime business scenarios/use cases and especially if the people writing those scenarios are business people nobody will say âhey letâs test that if the order is null the system should handle that somehow gracefullyâ.
So instead of having this dangerous possibility go unchecked and not handled gracefully a very cheap and simple unit test could help us verifying that we either throw an exception in case order is null or return false or whatever in matter of couple milliseconds.
That magnifying lens of unit tests will make our code more reliable, it will make it less likely to have simple but dangerous things like this go unchecked, itâs very cheap to write, it can be automated, itâs easy to read, easy to reason about and itâs very cheap to maintain. If someone somehow removed the line that checks the null-ability of the incoming order and if that somehow slipped through the code-review process, that unit test will fail and as a result the CI/CD pipeline will also fail.
The main point I wanted to make from this paragraph is the important concept of the âmagnifying lensâ that the unit tests have. Thanks to that magnifying lens that inspects your code âline by lineâ, it sets your mindset in a certain way, it forces you to think in certain ways and it promotes better thinking about the code you are writing. Please keep in mind that concept because we are going to use it a lot from now on.
With that point, Iâd like to end this article here. I am trying to make those articles âshort and sweetâ as they say so no one get bored while reading đ not so many people like to read especially nowadays đ, where people attention span has become the shortest ever.
In the next article I am going to talk about even more important benefits of unit tests and how they for example guide the design of the code and promote best practices and so on and so forth (letâs not get ahead of ourselves đ). Iâll also try to make it as technology agnostic as possible (not just C# or .NET thing).
Iâd appreciate the interactions, if you think anything could be improved or you would like to start a discussion please feel free to write a comment. I am hopping to build a community here not just writing my personal thoughts and experience.
Unit testing: Because life is too short to manually debug the universe
Thanks again for reaching this far and please stay tuned for more and a lot more, not just about unit test but a lot more.
Nice article! And interesting phrase âsociable unit testsâ. I didnt hear/see it before.
Tests are like code-stimulators + diagnostic in one frame - ensure that code is alive and healthy :)