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 :)