The benefits of microservices, such as increased application flexibility, scalability, business-centricity, and the ability to take advantage of diverse technologies are widely known. However, it must also be said that using microservices creates some significant difficulties for both software developers and testers. In particular, there are several important aspects QA engineers must consider before engaging in microservices testing.
Any testing strategy is aimed at testing software optimally. Achieving this goal largely depends on the combination of the different types of tests your strategy is composed of. As microservices, ideally, represent the smallest possible standalone pieces of business logic that interact with one another via a network, the importance and proportion of some types of software tests for microservices-based architecture is different than those they have in monoliths.
Correspondingly, regardless of its specific purpose, a capable microservices testing strategy must always include a large number of unit tests, a smaller number of integration tests, an even smaller number of components tests, a few end-to-end tests, and only some insignificant amount of exploratory testing. This is best illustrated by the following test pyramid:
Now, let’s look at the main testing strategies that can be used for microservices testing, and some of the peculiarities of applying each of these types of tests to microservices-based application architecture.
As previously mentioned, unit tests should constitute the bulk of microservices-related testing. This type of testing allows you to achieve the highest software quality in microservices testing.
As microservices embody and encapsulate each meaningful piece of business logic to make an application more flexible (meaning the smaller the better), they break application functionality into an immeasurably larger number of testable functionality units. Consequently, while testing a microservices app, you should keep your unit tests as small as possible, preferably, on the level of a class or that of a group of related classes.
While unit testing a microservices app, you can split your tests into solitary tests, meant to determine how objects are impacted when the functionality they reside in is tested, and sociable tests that measure the interactions between an object and its dependencies (as defined by the well-known software engineer, Martin Fowler). Solitary testing of microservices is employed to test the result produced by a single component. It allows you to efficiently cut off all dependencies of a component by using test doubles like fakes, stubs, mocks, dummies, and spies. As a rule, sociable testing of microservices is employed to test business logic units that perform complex calculations and/or have multiple transition states. To facilitate the sociable testing process, it’s recommended that you place the unit tests, pertaining to a microservice component, within this microservice component’s code repository. You should also use testing frameworks that are well-suited to the programming languages in question.
As each microservice is comprised of several layers, there are a few layer-specific unit testing nuances. Being aware of these subtleties can help you improve the testing process and achieve better testing outcomes. For example, the gateways that, along with collaborators, make up the resources and services layer, should be tested using solitary testing to avoid repercussions down the network. When choosing mock objects for the part of the gateways that reside on a different server, it’s best to use test doubles. When testing the domain layer, you should opt for sociable testing, as this layer contains transition states and calculations.
While unit testing of microservices is largely intended to test the interactions between the different functions within a microservice, component testing is used to gauge how the microservice performs as a whole.
Unit Testing should precede component testing. Component testing of a microservice is performed within this microservices code repository. Mock objects are used for the other microservices that interact with it.
Given that any microservices-based architecture is, predominantly, reliant on over-the-wire interactions rather than in-process software components, the role of integration testing here increases dramatically.
Microservice integration tests can vary in granularity. You can use the bottom-up, bottom-down, or sandwich testing approaches, and commence tests as soon as some two components of your microservices app are ready (i.e., under development), which can facilitate your bug-fixing process.
Contract tests are integration tests meant to verify the format compliance of the data transferred between a component and a consumer app, as it accesses the API (REST- or messaging-based) of this component. The format the data is to be compliant with is stipulated by a contract between the provider and the consumer. In other words, consumer-driven contract testing focuses on ensuring that this contract is observed.
Under consumer-driven contract testing, the contract is composed by the consumer, stating their expectations with regards to the interactions that need to be ensured and the format they are to take place in. A mock of the provider is then made based on these expectations. The consumer then tests their app against this mock and hands the result over to the provider for their existing implementation to be validated and adjusted accordingly. They also provide a suite of tests for the consumer-driven contract testing to be performed.
The role of contract testing in testing microservices is very important, in part, because end-to-end tests are less efficient for microservices-based architecture due to the impossibility of defining the full variety of ways in which a microservice can be consumed independently.
In addition to making sure that API endpoint code performs as expected, provides active endpoints, and includes no broken connections, you can use consumer contract-driven testing to discover bugs in consumer workflows and remove discrepancies in endpoint payloads and configurations.
When writing contract tests for a microservice, include only those parts of it that will be active.
As mentioned previously, end-to-end testing cannot be a widely used microservices testing strategy by definition, as any microservices-based architecture represents a collection of loosely connected services. Nevertheless, you cannot dispense with testing the work cycles of your microservices end to end.
Incidentally, the best approach to take is to use the technique called service virtualization, which allows you to mock multiple dependencies without having to use any actual mock objects. In other words, with service virtualization, you can mock the full variety of API-based external components, thus emulating a complete working environment for your microservice.
Microservices testing can be significantly different from testing a monolithic app.
The bulk of your efforts should be focused on sufficiently isolating your microservices from one another and verifying that the contracts between them are fully observed.
Are you looking for an experienced outsourcing partner to help test your microservices app?
Contact us today and tell us about your project.