Software testing for Microservices Architecture
Over the last few years, Microservices has silently but surely made its presence felt in the crowded software architecture market. The Microservices architecture deviates from the traditional monolithic application built where the application is built as a single unit. While the monolithic architecture is quite sound, frustrations around it are building especially since more and more applications are being deployed in the Cloud. Microservices architecture has a modular structure where instead of plugging together components, the software is componentized by breaking it down into services. The applications, hence, are built like a suite of services that are independently deployable, scalable and even provide the flexibility for different services to be written in different languages. Further this approach also helps enables parallel development across multiple teams.
Quite obviously, the testing strategy that applied to monolithic needs to change with the shift to micro services. Considering that applications built in the micro services architecture deliver highly on functionality and performance, testing has to cover each layer and between the layers of the service and at the same time remain lightweight. However, because of the distributed nature of micro services development, testing can often be a big challenge. Some of the challenges faced are as follows:
- An inclination of testing teams to use Web API testing tools built around SOA testing which can prove to be a problem. Since the services are developed by different teams, timely availability of all services for testing can be a challenge.
- Identifying the right amount of testing at each point in the test life cycle
- Complicated extraction logs during testing and data verification
- Considering that development is agile and not integrated, availability of a dedicated test environment can be a challenge.
Mike Cohn’s Testing Pyramid can help greatly in drawing the test strategy to identify how much of testing is required. According to this pyramid, taking a bottom-up approach to testing and factoring in the automation effort required at each stage can help address the challenges mentioned above.
- Unit Testing
The scope of unit testing is internal to the service and is written around a group of related cases. Since the number of unit tests is higher in number they should ideally be automated. Unit testing in micro services has to amalgamate Sociable unit testing and Solitary unit testing to check the behaviors of the modules by observing changes in their state and also look at the interactions between the object and its dependencies. However, testers need to ensure that while unit tests constrain the ‘behavior’ of the unit under test, the tests do not constrain the ‘implementation’. They can do so by constantly questioning the value of the unit test in comparison with the maintenance cost or the cost of implementation constraint.
- Integration Testing
While testing the modules in isolation is essential, it is equally important to test that each module interacts correctly with its collaborator and test them as a subsystem to identify interface defects. This can be done with the help of integration tests. The aim of the integration test is to check how the modules interact with external components by checking the success and error paths through the integration module. Conducting ‘gateway integration tests’ and ‘persistence integration tests’ provide the assurances help in providing fast feedback by identifying logic regression and breakages between external components which ultimately helps in assessing the correctness of logic contained in each individual module.
- Component testing
Component testing in microservices demands that each component is tested in isolation by replacing external collaborators using test doubles and internal API endpoints. This provides the tester a controlled testing environment and helps them drive the tests from the customers perspective, allows comprehensive testing, improves test execution times and reduces build complexity by minimizing moving parts. Component tests also identify if the microservice has the correct network configuration and its capability to handle network requests.
- Contract Testing
The above three tests provide a high test coverage of the modules. However, they do not check if the external dependencies support end-to-end business flow. Contract testing tests the boundaries of the external services to check the input and output of the service calls and test if the service meets its contract expectation. Aggregating the results of all the consumer contract tests helps the maintainers make changes to a service, if required, without impacting the consumer and also help considerably when new services are being defined.
- End-to- End Testing
Along with testing the services, testers also need to ensure that the application meets the business goals irrespective of the architecture used to build it and test how the completely integrated system operates. End-to-end testing thus forms an important part of the testing strategy in micro services. Apart from this, considering that there are several moving parts for the same behavior in micro services architecture, end-to-end tests identify coverage gaps and ensure that business functions do not get impacted during architectural refactoring.
Testing in micro services has to be more granular and yet, at the same time, avoid become brittle and time-consuming. For a strong test strategy, testers need to define the services properly and have well-defined boundaries. Given that the software industry is leaning in greatly towards micro services, testers of these applications might need to change processes and implement tests directly at the service level. By doing so, not only will they be able to test each component properly, they will also have more time to focus on the end-to-end testing process when the application is integrated and deliver a superior product.