With the continued growth and transition to microservices it’s important to ensure that the time and money re-engineering systems to modern, cloud-based solutions lead to tangible benefits to the organization. In this multi-part series, we’ll look at different components and pitfalls that need to be considered when modernizing to microservices.
In this blog, we’ll look at testing within microservices.
The Paradox of Microservices Testing
How do you test a complex distributed system made of many parts? The traditional way is to have a full environment with all the services deployed in it and perform end-to-end (E2E) testing in that environment. This forms a set of services at a specific version that becomes a potential deployable entity. There is an inherent paradox between the initial appeal to decouple microserves from each other, only to be able to confidently deploy the versions that have been verified to work with each other. The full advantages of innovating faster with a microservices architecture cannot be fully realized with traditional E2E testing.
The Painful Limitations of E2E Testing
Here are some other very painful limitations of using E2E testing:
- It provides delayed feedback from the time the code is committed, increasing the cost to fix issues.
- It is typically brittle and, as such, becomes very expensive to maintain.
- It requires a dedicated environment to run, which leads to teams waiting on each other (unless there is automation to create the environment).
Contract Testing Might Be Your Answer
This is where contract testing can have enormous benefits. The ability to test the interactions between two microservices in isolation initially sounds a little bit like black magic. In a nutshell, in the case of consumer-driven contracts, the core principle is that a consumer service (service A) describes and records its expected interactions with a mocked provider service (service B). The specific request from A to B, the response from B to A and any relevant state data to build that response are all recorded as a contract on the consumer (A) side. The provider (B) side can then replay the contract without A being online and verifies that it respects the consumer expectations when it executes its real code.
A series of contracts in place provides confidence that services integrate correctly, without the services being up at the same time and requiring a separate environment to run. It shifts integration testing left by running these tests as early as possible. They can run on developers’ machines and also in the CI pipeline. Developers can make changes locally knowing that they are not breaking other services. In addition, the contracts can be tested across multiple versions of microservices, ensuring that a specific version of a microservice is compatible with a specific set of versions of other microservices. In a traditional approach, this would require setting up separate end-to-end environments for each permutation of microservices versions.
Contract testing is a great solution, but as with any change, what is most difficult is changing people’s habits. Giving up the mental comfort that the pieces are tested together in an E2E environment and trusting a new paradigm for testing is difficult. We suggest doing it along with whatever exists today in E2E testing; building trust in the new paradigm. E2E testing can start to be trimmed down at a later stage. It should still be used, but can then focus on testing that the microservices are wired together properly in that environment: Are the endpoints reachable all the way down? Is the authentication and authorization working? Are the certificates between the endpoints trusted and set up properly? E2E testing does not need to focus on testing the variance in functionality depending on the input.
Need to catch-up? Previously, lessons included:
Part 1: The Importance of Starting with the Team
Part 2: Defining Ownership
Part 3: Process Management and Production Capacity
Part 4: Reserving Capacity for Innovation
Part 5: Microservices Communication Patterns
Part 6: Using Shadow Release Strategy
Part 7: Performance Testing Microservices
Part 8: Memory Configuration Between Java and Kubernetes
Part 9: Prioritizing Testing within Microservices
Part 10: Distributed Systems
In Part 10, our final lesson, we’ll be looking at Distributed Systems and why they fail.
Ready to modernize your organization’s microservices? Oteemo is a leader in cloud-native application development. Learn more: https://oteemo.com/cloud-native-application-development/