How TDD helps software architecture to be clean
This article was writen in english since english is a common language for software engineering area. If you want to read it in another language, I would suggest you to give a try with a translator to help you out.
Throughout my experience I heard many times how Test Driven Development helped to maintain the code in a good shape, keeping methods and classes small, avoiding code rigidity as it make easier to follow laws and principles. However, the point I want to bring up today is how TDD goes beyond the code and also impacts the architecture.
I am going to focus on how TDD makes the communication among micro services mature and bring up a discussion on how the test driven mindset enables the team to create reliable micro services, how it brings a user centered thinking and how it guides the project to avoid anti patterns.
Before we get started, I need to say that although TDD is a great support to achieve a clean architecture, it certainly is not the unique required knowledge. Some techniques as micro services, DDD and reactive patterns could be also necessary to tackle complexity. They all work together.
Relationship between testability and reliability
A messed code is hard to test, a messed architecture is hard to test as well.
But what I mean by a messed architecture? I mean an over-complex one! One with a lot of accidental complexity, where the wheel was reinvented two or three times, where there isn’t a good responsibility definition and all this makes the solution rigid, slow and difficult to rely on. You can remove all these complexities and the product will still be there.
In the last years I saw some Tom’s solutions (gif above) that became messed due to different reasons. There were applications too big that required a xxx-large machine just to run it: impossible to test efficiently. And also there were applications spliced in many places without a good responsibility definition, bad communication with each other, data integrity errors, dependent deployments among other issues: small changes in those systems were really a pain.
Fortunately, TDD appeared in my daily work and the adjective “testable” became one of the most important adjectives. That adjective has a close relationship with the system reliability. To start develop guided by test was an effective way to tackle the code/architecture complexity and a broad solution that changed the team mindset.
I picked up some points of the relationship between TDD and software reliability in the following list:
- TDD keeps the team with high code coverage, so you feel better to do a code refactoring without breaking features in production. The code, the architecture and the universe tend to chaos, if you are not able to do a efficient refactoring, your architecture is going to be messed up in a short time.
- TDD avoids accidental complexity. Let’s think together: if you start coding the tests and lot of unnecessary complexity jumps from hell, and testing becomes a pain (a lot of scenarios, failure cases, dependencies and so forth), you do have to stop and rethink your solution. We all know that we have to improve the solution to be testable.
- TDD guides us to use community patterns instead of reinventing the wheel since there are a lot of tools to test those patterns already available. For instance, if you need to create a feature to share something among other services, it is pretty easy to test HTTP’s patterns with RSpec, Scalatest, Cucumber and other frameworks tools.
- By running the functional tests, the visibility of non functional requirements comes up daily as the test automation run in the pipeline. For example, if a functional test ran in 13 seconds for the past 4 weeks and after some code change it degraded to 19 seconds: you are able see which commit added that degradation in the application performance, thus you can take an action to avoid your application to be slow. If you have to fix it later, probably there will a lot of code changes you should consider so won’t be that easy to fix the issue.
- A testable code tends to be aligned with market technology. To create a test we need to think how to make sure the functional requirements are met. For instance, I have seen testable code tending to use containerization technologies because it is pretty simple to test the whole application inside a container that is the same one that is going to be used in production. That impacts the software architectures in many good ways.
Team members journey perspective
Currently at Stone I’m dealing with the experience of building a team with a QA mindset, TDD is a key practice to ensure it. Those are the points where TDD supports the team onboarding:
- Even for people which are pretty new coding, TDD helps them ensure the quality;
- Tests are a live documentation whom teach the domain side of the code;
- New hires grow up with a testing mindset that maintains the system reliable;
Infrastructure perspective
During some conversations at Stone we talked about good questions of infrastructure tests and one of the points was that they not as mature as software tests are. Does the infra tests sometimes test the test itself? Does tools like liveness probe from Kubernetes do what the infra tests was supposed to do? Does the performance tests already an infra test was supposed to do?
Anyway, the key point on the talk was that a clean architecture (a testable one) is easier to provision and monitor. Services that runs in containers with their inputs and outputs tested as a whole, that is not a Tom’s solution, enable the team to have flow’s visibility in any environment.
Those are my thoughts on how TDD impacts system architecture that I have seen in the last 4 years practicing it. I hope it helps on your journey.
Share you feedback and thoughts, it would be great to hear other perspectives.
Thanks Marcus Lucas, Diego Morales and RafaR for improving this article.
References
My developer life — José Roniérison
TDD By Example — Kent Beck