A controversial view on unit tests
Nowadays, we live in a unit-test-oriented world. Writing tests before writing code – is the prevailing approach. It’s all about them. However, it’s worth noting that many companies recruiting specialists with TDD requirements don’t even use it. Tests are undeniably beneficial. They save time and money detecting issues as soon as possible. But there is a downside to tests which is not commonly seen.
Types of tests
Unit tests are not the only tests we can implement. Depending on your application some of them may or may not be applicable. Let’s take an example: a web system built with separate frontend and backend using microservices architecture. We will focus on code/service tests (backend only) so we’ll skip infrastructure tests.
- Unit tests – you test small fragments of code, usually one method with mocked dependencies. You check if the given parameters give you expected result such as correct response and expected exception / failure. Unit tests also check if a dependency was called, for example if a log event was added or if an interface’s method was called.
- Integration tests – the definition depends on the individual, so I’ll describe mine. It’s a unit test but tested with as wide context as possible without running an app: you create an instance of entry point (controller/command/query) and mock the furthest dependency (e.g. database connector). It tests a bigger scope of the code without starting the application. No database nor dependant services are required.
- backend e2e – you test unchanged code without any mocks inside. If your application can handle repetitive tasks or you can clean the environment, you can execute tests against real environment, otherwise you can create a database for example from a docker image, and use tool like Wiremock to mock other services that are not the subject of the test.
Value of tests
At first, let’s think about the app we’re building. Regardless of the commissioner (which sometimes is ourselves), there’s always a problem for which our application provides a solution. In commercial applications, you’re not developing software for the process of development, but for the end result, which is real value.
Let’s assume we build a loan form. The value for the company is to give loans to the clients, so they can earn on commission and margin. The worst thing for the business would be to stop giving loans.
Downtime may happen for example because of a bug in the code. The sooner we detect it, the sooner it will be fixed and the cheaper the fix will be. Depending on the company size, deployment processes can be time consuming, with a lot of people involved and in worst case the application will be stopped till fixed or rolled back.
What type of test should we start with
Code quality is important, but it doesn’t give business value (bad code creates technology debt, but if it works then it works). The thing we should protect the most is business value of the product.
For most of the projects I would NOT recommend to begin with unit tests. These tests are pouring concrete our code – development slows down, because even the simplest change requires to change several tests and test cases. It doesn’t guarantee proper working of the application but it’s smallest parts separately, additionally unit tests are sometimes incorrectly written not to confront against expected behaviour but to assert what was created (even mistakes).
I’d recommend to create backend e2e for all new projects before any other tests. The code may change, may be pretty or awful – at first we have to assure the application gives us what it was made for – the business value. After we have this, we can move to other types of tests.
It may sound strange to write backend e2e tests in the beginning of the project, if architecture is not clear, there may be refactors, several things can change, but the contract and behaviour of the application is changed at last. Refactors however should not change how the app works, so if any e2e test is broken it means we have an issue with the refactor, but majority of unit tests will be broken and that’s fine, because the code changed.
Unit tests will often be false-negative when the change is done on purpose. E2E may take their time to be done correctly but won’t collide with the development and will give a valuable feedback to the developers and other team members.