Why TDD Can Be Enemy in Microservices Architecture
Cherish DEV was contacted by the company T (due to a strict NDA, we can’t disclose the name). This company was about to move from a monolithic architecture to a microservices one. Their goal is to improve the scalability and independence of their product development. They had been successfully using the Test-Driven Development (TDD) approach for several years and treated it as the core of their development process.
However, while TDD helped them maintain high code quality in the monolith, it caused problems when migrating to microservices.
Over time, the team noticed that the time spent on maintaining and updating tests became more significant. Tests began to delay the development process rather than help it. Moreover, many of the tests developed within TDD were found to be ineffective in the new microservices context.
In this article, we'll take a closer look at their case study, understand what went wrong, and discuss how testing in a microservice architecture can be approached differently to avoid similar problems.
1. Benefits of TDD for Monolithic Applications
Test-Driven Development (TDD) is a software development method in which the developer first writes tests that determine the desired behavior of a function or method, and then writes code that allows those tests to pass. This not only helps ensure high quality code, but also promotes a deeper understanding of how a particular function or method should work.
TDD has a number of advantages, especially in the context of a monolithic architecture:
1. Continuous Feedback: TDD provides continuous feedback to developers about the functionality of their code, allowing errors to be identified and corrected more quickly.
2. Improving code quality: Because tests are written before code, TDD helps keep code clean and minimize errors.
3. Documentation: Tests written within TDD can serve as a form of documentation, allowing new team members or other stakeholders to quickly understand what a specific piece of code does.
4. Architecture improvements: TDD practice can lead to a cleaner and more modular architecture, since it requires functions and methods to be independent and uniquely defined.
TDD can be ineffective for a microservice architecture due to the coupling and interdependency between different services. It is difficult to write comprehensive tests in a microservice environment because each service typically interacts with other services, and changes to one service can affect the performance of others. Moreover, since each microservice is typically developed by a separate team, coordinating tests between teams can be a complex and resource-intensive process. Thus, while TDD can be useful for ensuring code quality within a single microservice, its effectiveness is reduced in a more complex microservice architecture.
2. Company Product Overview and TDD Applications
Company T is developing a large online trading platform. They service many types of products, from electronics to food. As a monolithic application, the platform concentrated all functions including inventory management, order processing, content management, customer service and more into a single code base.
When they decided to move to a microservices architecture, they started by gradually separating individual functions into their own microservices.
Application of TDD
Company T decided to begin its transition to a microservice architecture by developing an order processing microservice.
Development process with TDD:
1. Definition of functional requirements
The T developers started by defining the basic functional requirements for the order processing microservice. These requirements included the following basic functions:
2. Writing tests
Given these functional requirements, the T developers wrote a corresponding set of tests. Each functional requirement was translated into one or more tests.
For the "Create Order" function, the developers wrote tests that checked that:
For the "Order Processing" function, the developers wrote tests that checked that:
For the Order Tracking function, the developers wrote tests that verified that:
3. Writing code
After writing the tests, the developers wrote code that would allow these tests to pass successfully.
If any test failed, the code was reworked until all tests passed green. This could include fixing bugs, refactoring code, or adding new features.
The entire process was iterative: writing the test, writing the code, validating the test, refactoring the code, and so on, until all the features were fully implemented and all the tests passed.
Problems Caused by TDD
At first, everything went smoothly. However, when they started integrating the order processing microservice with other parts of the system, problems arose.
With the choice of microservice architecture, TDD began to cause undesirable effects.
领英推荐
TDD, which had proven itself in a monolithic architecture, turned out to be incompatible with the new microservices structure. This impacted team efficiency, increased support time, and impacted service performance, which could ultimately impact customer trust and the company's financial performance.
3. Possible alternatives to TDD in microservices
Let's look at alternatives that may be more suitable for use in a microservice architecture.
1. Contract testing
Contract testing is an approach in which tests are created to test the interactions between individual services. These tests typically focus on interactions across specific "contracts" or "interfaces", ensuring that each service communicates correctly with the others.
2. Integration testing
Unlike unit tests, which test functionality within a single service, integration tests test how multiple services interact together.
3. System level testing
System-level testing involves testing the entire system as a whole. This usually involves running real use cases and verifying that all services work together as expected.
4. Testing at the application level
Here tests are written to test functionality at the user level. This involves testing how users interact with the system and making sure everything works as expected.
Each approach has its pros and cons, and the best choice will probably involve a combination of different strategies specific to the context and needs of a particular development team.
4. How Cherish DEV helped Company T overcome the problem
Based on our analysis of Company T's current testing processes, we saw that their TDD approach didn’t provide adequate test coverage for the microservice framework. We developed and implemented a new testing strategy that included the following aspects.
1. Contract testing
We implemented contract testing using Spring Cloud Contract. This allowed us to ensure stability and reliability of interactions between microservices, while ensuring their independence from each other. Contracts were agreed upon in advance and used to write tests that ensure that each microservice meets its obligations.
2. Integration testing
We helped the T team implement automated integration tests that tested interactions between microservices. We used TestContainers to mock dependent services and create an isolated test environment for each service.
3. Continuous integration and continuous delivery (CI/CD)
We implemented a CI/CD process using Jenkins and GitLab CI. By doing this, we provided quick and constant feedback to developers about the status of their code. We implemented a continuous testing practice in which tests were run automatically whenever the code changed.
4. Monitoring and logging
A monitoring and logging system was implemented using Prometheus and ELK stack. This is how we began to monitor the performance of each microservice in real time. This provided an additional level of control and visibility for Company T.
Conclusion
Transitioning from a monolithic architecture to a microservices organization can be challenging. One of the main lessons we can learn from Company T's experience is that time-tested approaches such as TDD may not always be effective in the new microservices context.
Here are some key takeaways:
As we can see, microservice architecture presents significant opportunities and benefits, but also introduces new challenges to the testing process. Cherish DEV's approach to solving this problem allows for increased reliability and testing efficiency while maintaining the benefits of Agile development that microservices provide.