The Age of Microservices Testing
As many companies adopt microservices for rebuilds and new applications, many will realize that the established 'Test Pyramid' approach to testing is considerably less practical than it was with traditional applications. The Test Pyramid emphasizes unit tests focusing on the application's functions and code. These comprise the bulk of this testing strategy, reflected in their position in the more extensive bottom pyramid section.
When we switch to a microservices architecture, we find that unit tests need to provide more application coverage. Unlike standard applications, microservice tend to contain less code and more workflows of multiple connected consumers (services that create a request for external services) and providers.
Consumers are services that start a request for external services. For a practical example, let's consider a checkout service that calls an external payment service to process payments. Finally, providers are services that receive and process a request. These preferences resulted in the development of the Test Honeycomb; we will explore more about this approach in this blog post.
A brief overview of Microservices
To better understand microservices, we must look at where they came from. The concept of microservices has probably been used in various forms for many years; its association with a unique way of building software came from a meeting attended by a handful of software architects. This group saw similarities in how a specific set of organizations built software and gave it a name.
As James Lewis, who was in attendance, remembers it:
At the end of our three-day meeting, one of us called out a theme that year; it had been clear that many of the problems people were facing in the wild were related to building systems that were too big. "How can I rebuild a part of this," "best ways to implement Strangler," etc.
Turning that on its head, the problem became, "how can we build replaceable systems over being maintainable?" We used the term micro apps; I seem to remember.
James' remembrance of the microservices origin story is essential for the historical record (Extracted from the book Microservice Architecture).
Microservices are part of a modern development model focusing on small, loosely-coupled, independently-deployable services instead of a single monolith of code. They transmit with each other via networks and, as an architecture option, offer many options for solving the problems you may face. It observes that a microservice architecture is considered on numerous collaborating microservices. In addition, microservices have the advantage of being technology agnostic.
Microservices help to expose the business capabilities that they encapsulate via one or more network endpoints and then communicate with each other via these networks, making them a setup of a distributed system. They also encapsulate data storage and retrieval, uncovering data through well-defined interfaces. In this case, databases are hidden inside the service edge.
The picture changed when microservice-based apps came about. As the name suggests, microservices represent small, independent, loosely coupled modules that build the app's overall functionality. They can be altered, decomposed, and deployed independently, which in turn, gradually enhances their agility, flexibility, and scale.
However, even though microservices represent a significant, innovative leap within the software development industry. It could be more evident that this leap is in the right direction if we can guarantee our microservices high quality.
The Inefficient Testing Pyramid strategy for Microservices
The traditional approach to the testing pyramid is highly efficient for a monolithic application. For a long time, this strategy was used by most enterprises in the software world. However, in a microservice world, there are better approaches than this approach for testing a microservice-based application.
The most considerable complexity is not within the microservice but within microservices interactions. The above approach focuses on unit testing rather than integration testing. That is why such an approach can be harmful to the microservice application.
Unit tests are followed by integration tests that validate the relations between two or more connected components. While contract testing is not officially part of the test pyramid approach, we should add it in a similar section as Integration Testing for awareness purposes.
At the highest and granular part of the pyramid are the E2E tests (excluding Component tests), which test the result of application workflows; as we move up the pyramid, the cost and effort needed to develop and run each type of test increases. It is one of the reasons unit tests are most used, as lower effort and cost typically translate to better developer productivity. Still, for quality purposes, more than the unit test is needed.
Something less costly must be there; we require a different approach to eradicate the dependency on external services for tests to be conducted and simultaneously enable QAs to confidently test changes before moving them to production.
The Microservices Testing Approach
Having too many tests in Microservices, which must be small by definition, also restricts how we can change the code without changing the tests. Therefore, Contract Testing is a more appropriate strategy for our Microservices testing.
Unit test cases only cover a single microservice. Therefore, we must have several unit test cases for a microservice, where the unit test depends on the language we are using to develop that microservice. It also depends on the framework being used in development.
A unit can consist of a line of code, a method, or a class. Unit testing refers to testing a particular unit for any bugs or issues. Optimally, the smaller the unit is, the better it is because this allows testing on a more granular level and gives a more accurate view of how well the overall code is performing. The most crucial factor of unit testing is that, by running many small tests, instead of one big test, you can complete the testing process in a matter of seconds or minutes instead of hours, depending on the size of the code.
An integration test is the opposite of a unit test. In the microservice architecture, integration testings is typically used to verify interactions between different layers of integration code and external components such as databases and REST APIs. In addition, integration tests can be used to test other microservices, including data stores and caches.
Microservice integration testing validates that the distributed system is working smoothly with external dependencies and checks that all external or internal dependencies between the services are present as expected. Unit testing alone doesn't provide a sufficient output for the distributed system's behavior, so we also need to use integration testing. In addition, we also need some other approach to testing for microservices.
There are two perspectives regarding Contract Tests, consumer, and provider. As we might imagine, the consumer viewpoint is that of an entity using microservices. The provider perspective is that of the entity providing the service. It's the discrepancy between "your service isn't working the way that the definition said it would" (consumer) and "The service is working according to definition" (provider). Thus, it all comes down to the definition, in other words, the contract. In plain English, Contract Testing is writing tests to ensure that your microservices' explicit and implicit contracts work correctly.
Contract testing is different from unit testing or integration testing; it doesn't test the component deeply, but rather, it only tests the data structure with the required attributes for the input and output of service calls.
End to End Testing
The last part of our microservice testing approach is end-end testing; in end-to-end testing, an application is tested to check whether or not it has a complete flow from the beginning to the end. End-to-end testing contains all the critical functionalities for any bugs or anomalies, such as communication within or outside the system, the application's interface, the database, the network, and other components.
In a microservice-based application, end-to-end testing provides value by covering the gaps between the services.
ACCELQ can help with microservices testing by providing a platform to design, execute and manage tests for microservices-based applications. It enables QAs to write test cases in a natural language format, eliminating the need for technical coding skills. ACCELQ also has features for organizing tests into suites, parallel execution of tests, data-driven testing, and reporting capabilities. Further, it integrates with CI/CD pipelines, allowing for seamless testing in the DevOps environment and promoting efficient microservices testing.