More than ever, businesses need to adapt to how and where their customers are wanting to engage. Growth, revenue, and engagement via traditional (web) channels are being exhausted. New digital mediums need to be stood up quickly when significant market segments are identified. Microservice architecture is a response to that demand which aggregates the core ‘business capabilities’ to a set of services that any new digital experience can quickly leverage.
While servicing the desire to stand up new digital mediums quickly, microservice architectures also expose unique challenges for responsible, quality-oriented, and reliable software development and demand additional considerations for testing methodologies. Two top considerations that all testing methodologies must address are “what to test” and “when to test”. The Test Pyramid provides a blueprint for addressing both concerns.
Let’s explore how the Test Pyramid can be applied to a microservice architecture, and how to overcome unique challenges presented in that application.
Testing pyramid application to Microservice Architecture
To achieve a greater understanding of how the Test Pyramid layers are applied, we will introduce a 4-tier microservice architecture seen in Figure 1, below. A microservice architecture is comprised of Components. It is important we define the term “Component” as it applies to the different flavors of tests. A software Component is a unit of software that is independently replaceable and upgradeable.
Figure 1: 4-tier microservice architecture example
The example 4-tier architecture is comprised of the following tiers:
- Client Tier – Client application Components consume one or more delivery applications in order to propagate data from lower tiers into the client application
- Delivery Tier – Aggregated API Components are exposed for client consumption through delivery application Components intended for consumption by different client applications
- Aggregation Tier – Services are aggregated by various API Components that add or consume messages on the Stream Processing Service
- Service Tier – Connected through a Stream Processing Service, individual Service Components may be dynamically added to or removed from the Stream Processing Service’s bus
Unit Test Layer in a Microservice Architecture
Unit tests should be prolific through all tiers and Components of the microservice architecture. Every deployed Component in any tier of the architecture should have Unit Tests defined within that Component’s code repository.
A challenge that can arise with Unit Tests is that various Components likely to be implemented in different languages. Languages have standard and third-party unit test frameworks, making switching between various Component code repositories difficult. The issue is compounded as developers attempt to reliably run and update unit tests. To overcome this challenge, select unit test frameworks for each Component’s language that correspond across languages. By choosing test frameworks that share similar testing philosophies and testing structures, a developer able to switch between Component code bases and Unit Test suites with greater ease.
Component Test Layer in a Microservice Architecture
In the example 4-tier microservice architecture example from Figure 1, each Service, API, Delivery App, and Client App is considered a Component. Component tests should be implemented within each Component’s code repository where all dependency Components are mocked. For example, Service Tier Components rely on messages placed on the bus by other Services. Therefore the Component Tests for a specific Service should rely on mock messages rather than instantiating any other Services.
A challenge that can arise with Component Tests is ensuring interfacing Components fulfill the same interface contract for testing. This challenge is especially evident when the development of different Components is being handled by disparate developers or teams. To overcome this challenge, it is helpful to implement a schema-based interface that both Components incorporate into their interface implementation. Schemas may be defined in a code repository separate from all Components, providing a single source of truth for cross-Component interactions.
Interface Test Layer in a Microservice Architecture
Once the Components that comprise the example 4-tier microservice architecture are deployed to an environment, communication between components may fail due to various configuration and environment considerations. The Interface Test layer test suite is comprised of lightweight testing mechanisms for ensuring Components are communicating.
A challenge that can arise with Interface Tests is ensuring that Component communication configuration. Specifically, ensuring configuration is handled similarly between local development and deployed environments. This challenge arises when developers run Interface Tests locally vs deployed environments. To overcome this challenge, it is helpful to use a virtualization strategy in order to stand up and link Component processes in a consistent way independent of environment. In addition, Interface Test suites need to be configurable to run against local or deployed environments.
System Test Layer in a Microservice Architecture
In the 4-tier microservice architecture example, end to end data transfer occurs at the Delivery Tier layer. A single System Test layer test suite should include harnesses for producing sessions that can properly connect to Delivery Applications. Session considerations may include cookie tracking, request headers, etc that are specific to various Delivery Apps. It is worth noting that depending on the deployment configuration of the Aggregation Tier, it is conceivable that System Test layer tests could execute against the Aggregation Tier directly.
Like Interface Tests, a challenge that can arise with System Tests is ensuring that Component configuration is similar between environments. This challenge is overcome by using an operating-system-level virtualization strategy in order to stand up and link Component processes in a consistent way independent of environment. In addition, System Test suites need to be configurable to run against local or deployed environments.
Functional Test Layer in a Microservice Architecture
The Functional Test layer is focused on the end-user functionality experienced on the client application. Since this layer focuses on the user experience it is unaffected by microservice architecture. Functional Test layer test suites should be created in order to support the specific needs of each Client Application. For example, a web application would require the a Functional test suite that manipulates a UI in a browser. Alternatively, a Functional Test suite for a native mobile application would be manipulated differently.
Functional Tests challenges in a microservice architecture include organization of Functional Test suites between client applications. This challenge arises as each client application requires its own Functional Test suite in order to exercise unique UI. This challenge is overcome by building a Functional Test suite capable of handling client applications following the Page Object model.
As we have seen, the Test Pyramid provides a framework for defining the flavors of tests required for Microservice Testing. While microservice architectures present unique challenges for testing, these challenges are not insurmountable as summarized in the below table.
|Test Pyramid Layer||Challenge||Solution|
|Unit Tests||Cross-language implementation of Unit Tests across Components||Select Unit Test frameworks that share similar Unit Test philosophy and implementation details|
|Component Tests||Interface contract fulfillment||Schema-based interface definition for Components|
|Interface Tests||Cross-environment Component communication configuration consistency||Virtualization strategy|
|System Tests||Cross-environment Component communication configuration consistency||Virtualization strategy|
|Functional Tests||Functional Test suite consistency||Page Object Model implementation in Functional Test Suite|
As discussed, a testing framework gives developers high confidence that their code does not introduce unintended bugs. Combining this with monitoring tools and automated CI/CD delivery pipeline allow for bugs to be uncovered quickly. Where bugs are uncovered, new tests are added so that the testing framework is strengthened. The result is a test platform that assists developers in their goals of quality software and fast, iterative development.