Unit testing, module testing, system testing, integration testing…. Quite a lot, right?
Today we would like to touch on the topic of testing – what kind of levels we may have, how to appropriately map them to the user scenarios, what kind can be more useful in one scenario, and what in the other scenario and do we always need levels of testing?
The most common level of testing is unit testing, which comes with checking the smallest piece of the code. It is also the easiest one as a community and market provide many different frameworks, which make such kind of testing very easy. Additionally, it is mostly project-independent – in other words, no matter if you are developing a big system like software for cars or a small application for mobile to remember a shopping list – unit testing will look the same because they are checking very small pieces of code, which alone is not strongly connected to the whole system.
Unit tests check the simple logic – if I got the message A, I shall transform it to the message B by using this and that algorithm. Unit tests are great for such scenarios – no matter what protocol you are using for sending messages, no matter who is the sender, and who is the receiver – they can just check if the transformation of a message is correct.
However, because they are simple – they test only simple things. Unfortunately, developers overuse them because they are the simplest way to reach proper code coverage. They are also relatively simple to write, so people do not spend much time on testing, which often they do not like. Additionally, a review of such tests can be done by a person who is not familiar with the project itself – it can be a reviewer who checks only the quality of the test, not its correctness (what is a difference you can find here: https://rsoftcon.com/blog/types-of-reviewers/).
Because of the above reasons, a majority of projects have only such kind of testing which is not a good approach – more disadvantages of using Unit Tests can be found in one of the previous stories – https://rsoftcon.com/blog/why-unit-tests-play-chinese-whispers/.
The next level of testing varies between projects – it depends on complexity, frameworks, or areas of development (automotive, telecommunication, etc.). If we would like to split levels of testing into detailed ones, the level above unit testing we can call – module testing.
This kind of testing focuses more on interactions between units (which are tested of course by unit tests). It requires the initialization of more code and seeing a broader picture. Often it also needs a new framework or modification of available ones to cover more specific scenarios. It starts to rely on the project’s content but is it not yet a strong relationship. At the same time reviewing such tests may require knowledge of the project’s subject, which causes enabling the other type of reviewer (https://rsoftcon.com/blog/types-of-reviewers/). Such kind of testing is sometimes introduced even without planning it – when someone writes unit tests and starts to cover more units at the same time.
Module tests cover interactions between units, so they can catch more corner cases and avoid problems related to unit tests (https://rsoftcon.com/blog/why-unit-tests-play-chinese-whispers/). They are recommended when the complexity of a project is visible even in small software areas. For instance, when units exchange messages which are the results of many different inputs, and use different algorithms for transformation. In such cases, module tests can catch side effects, unexpected results, and other critical code paths.
Above module testing we have tests that cover the whole system or a couple of systems – it depends on the size of the project.
For system testing we can still rely on simulators – if your project contains a couple of systems that talk to each other, it is good to first test them in a black-box approach, so to check how system A behaves in case of different inputs, how system B behaves, etc. In such scenarios, simulators are external systems (because system A may get input from an external system) and internal systems (in case of system A sends messages to system B, so system B is simulated). There are also other advantages of this approach, including mapping to documentation, but we will cover it in the next stories.
While system testing can still use simulators, integration testing is a great place to start integrating with real systems. By real systems, it means various things like:
- Cloud Providers,
- APIs for external systems (like search engines)
- Real devices (drivers, bare metals, etc.)
As we can suspect, such kind of testing is quite expensive as it sometimes also involves manual configuration of tests or paying for usage of external systems. It means that we shall not rely on them in terms of finding some very corner cases, results of low-level algorithms, and so on (unless we are working with high-risk systems like in the banking or medical sectors). But they are great for checking if our assumptions are correct, if external systems answer with correct structures, if our systems send proper messages etc. They are also very important in terms of specification because it is a level of testing that can be mapped to acceptance criteria written before (if, of course, a company writes specification – see more here: https://rsoftcon.com/blog/documentation-and-specification).
On top of integration testing, we might also mention manual testing. This is a special kind of testing because it fully involves a dedicated tester. The whole system is checked manually, based on specified scenarios and acceptance criteria. As you may suspect, on this level of testing real systems are used with deployed software. This kind is highly recommended for applications that require good User Experience and contain a lot of UI changes.
All above can be translated into a simple picture:
In the perfect scenario, we have our codebase covered by every level of testing. What is more, tests complement each other – while unit tests are checking the low-level algorithms, module tests check communication with units, system tests check communication with modules and integration tests check communication between internal and external systems. It does not mean your company needs all of them, but it is good to remember that every level of testing increases your software quality.
In this article we talked about different levels of testing, starting from unit testing and ending at integration testing. We focused on differences, advantages, and disadvantages. However, they are not all kinds of testing – we only tried to show you how you can split your testing into levels to properly cover your codebase. Other kinds of testing could be – A/B tests, performance tests, robust testing, penetration testing, security testing, and many more. Usage of them highly depends on your project, which is not in the scope of this story.