In the life cycle of software, the maintenance phase is known to take the longest period of time. To ensure that the code is maintainable and does not become a lock-in for stakeholders, it must be subject to continuous quality assurance. It makes sense for this quality assurance to be automated, as manual tests are time-consuming and prone to errors.
Test Driven Development, or TDD for short, refers to automated testing before the components are created, rather than afterwards. Although this may be more time-consuming in the short term, it saves a lot of resources in the long run. According to Kent Beck, an anti-pattern goes hand in hand with the neglect of test automation. He calls it the "no time for testing" vicious circle: "The more stress you feel, the less testing you will do. The less testing you do, the more errors you will make. The more errors you make, the more stress you feel". This quote speaks for itself: more time pressure means less testing, which results in errors, which in turn results in more time pressure.
For this reason, in this article I will look at the advantages and disadvantages of Test Driven Development and the possibilities that this type of test scaling offers for software development.
The structure of software tests
Software tests can be divided into unit, integration and E2E tests. The test pyramid takes up this classification and adds the factors of number and effort. Many fast unit tests form the base of the pyramid, with a few slow E2E tests at the top. The link is formed by the integration tests.
Tests also serve to document the semantics of the software. They describe the expected behavior and serve to verify the correct function. The behavior of the software is either changed deliberately when new functionality is implemented or unintentionally during refactoring. To prevent the latter from happening, the code base must be covered with unit tests.
Martin Fowler answers the question of how much test coverage should be as follows: "The best measure for a good enough test suite is subjective: How confident are you that if someone introduces a defect into the code, some test will fail?". This security that tests offer is the prerequisite for refactoring.
Refactoring as an opportunity for improvement
Refactoring consists of many small changes to the code structure, but not to the behavior of the software. This continuous refactoring maintains the maintainability and flexibility of the system. However, it requires fast feedback from the test system. This fast feedback is only possible if there are sufficient unit tests. E2E tests, on the other hand, are slow and are not suitable for providing feedback.
The inverted test pyramid
If you still rely on E2E tests, this is an anti-pattern. I call it the inverted test pyramid. This anti-pattern consists of a software project being supported by slow E2E tests instead of unit tests. This slows down the development process as a whole and the necessary refactoring of the software is lost sight of. Instead, considerable efforts are concentrated on writing new E2E tests and maintaining the existing tests.
The lack of tests makes refactoring more difficult, which in turn leads to code that is difficult to maintain. This code is usually difficult to test because basic requirements for testing are often missing. For a software system to be easily testable, the components must be loosely coupled. A lack of dependency injection, disregard for the law of demeter or the excessive use of static methods lead to a strong coupling of the components. This means that the lack of consistent TDD in larger software projects almost inevitably leads to a dead end.
Breaking through the missing tests devil's circle
It is only through the "test first" approach that the vicious circle, in which technical debt increases, becomes an incremental development process. Just as testing takes place before implementation, the test result comes before the test implementation. Kent Beck distinguishes between the result of the test and the test algorithm and recommends starting with the assertion in the unit test. The method is derived from the expected result. For example, if you want to test whether a token contains a certain value, you start with the expected value and work in reverse order from decoding the string to generating the token.
Test Driven Development as part of agile software development
Test-driven development is just as much a part of agile software development as refactoring. In fact, it is not possible in the long term without it. The requirements of the stakeholders involved in a project result in continuous changes to the software. Only tests and refactoring enable the adaptability of the software.
Martin Fowler summarizes this idea as follows: "A healthy code base maximizes our productivity, allowing us to build more features for our users both faster and more cheaply. To keep code healthy, pay attention to what is getting between the programming team and that ideal, then refactor to get closer to the ideal".
Article by Mischa Siebert, Consultant at objective partner