Software architecture has evolved significantly over the past three decades — and approaches to software testing have had to change to keep up. Here, Vinh Pham, a Senior Test Team Manager at NashTech, discusses best-practice approaches to testing today's microservices-based apps.
Software architecture constantly evolves: from the spaghetti-like designs of the 1990s, we moved to lasagne-like layered architectures by the early 2000s. Today, those architectures are more like ravioli — little packages ('microservices') floating in a sauce.
A microservices architecture enables software apps to be built as a suite of smaller services, which communicate among themselves using defined APIs. Compared with a traditional 'monolithic' architecture, a microservices architecture provides the flexibility to build an app using different programming languages and technologies. It also increases scalability, and is better suited to a continuous integration and deployment (CI/CD) or DevOps approach.
As with any new architecture, however, a new approach to testing is needed. For a microservices architecture, the testing strategy must cover aspects such as how the services communicate, the security of each service, data consistency, and the influence of each service on the others. It should combine functional tests, non-functional tests and tools tailored to each project. And it should support testing of the component services separately, and the behaviour of the system as a whole.
A microservices architecture also brings a number of testing challenges. To write test cases correctly, we need a deep understanding of each service. We also need a clear understanding of how each service should:
There should be a clear request/response for each API endpoint in use, and the ability to run services from third-party apps or partners.
To verify that a microservices-based app is working correctly, we apply a five-layer approach comprising unit, integration, component, contract, and end-to-end tests.
Allows us to understand whether the smallest units of service — smallest possible testable parts of an app — are running as expected, either at class level or at the level of a small group of related classes defined by programmers. Unit testing can be split into:
Gets around the complexity often exhibited by microservices-based apps by testing each part of the app (component or microservice) in isolation. We test each component alone by replacing its dependencies using test doubles or mock-ups of the other services. Component testing helps make testing of microservices easier, faster and more reliable.
Provides the opportunity to execute the communication paths and interactions among components or microservices to make sure the dependencies are working correctly. We can use bottom-up, top-down or sandwich/hybrid approaches to validate that the microservices are working together smoothly to satisfy the business requirements.
Allows verification that the explicit and implicit contracts of your microservices work as advertised. There are two perspectives:
Contract testing focuses on making sure that things work as advertised in terms of the agreement. It means verifying the attributes or format of the data which is transferred via the API, as well as the responses. It ensures that the API endpoint code runs as expected. Additionally, we can use the consumer contract–driven testing to discover bugs in consumer workflows.
Aims to test the entire process and all the user flows of the app to check that the business processes work correctly and smoothly, and satisfy the requirements. It includes all services and database integrations.
Every moving part of the microservices-based app is covered in end-to-end testing. It also covers the gaps between the services, and makes sure that the dependencies between microservices are tested.
In addition to the five key testing layers, we perform security testing to ensure the request/response of APIs in data transfers is secure, especially when third-party services are used by the microservices app.
Performance testing (or 'load tests') is necessary in a microservices architecture because the app is built from multiple services which are isolated, yet depend on each other. The app will often involve third-party services from external environments (PaaS). If these don't work well, it can have a big impact on the overall performance of the app.
With microservices-based architectures, automation has an important role to play in testing. Automation helps to ensure that — throughout incremental releases and following updates in service API versions — regression testing can be performed quickly and easily through each testing layer to provide assurance that the entire app is working correctly.
The return on investment in test automation depends on the specifics of the app architecture and the frequency of changes. Using tools such as Postman or SoapUI for integration layer testing can be an advantage.
To learn more about NashTech Software Testing Services, email firstname.lastname@example.org and a member of the team will be in touch.