Test-driven Development

Using agile methods is not a 100% guarantee of better software. This is due to the fact that the agile context constitutes a major challenge for many traditional methods of quality assurance. In a system that frequently changes, how can we permanently ensure that the components that are deemed functional now do not fall back into an undefined state due to future improvements?

While the waterfall model provides for testing only at the end of the project, and the frequently used V-model (see Figure 1) also has a clearly defined chronological sequence, in an agile environment, it is crucial that tests are, if possible, always done under the exact same conditions and with the least possible effort because of the frequency at which they have to be done. Furthermore, they should be ready as soon as possible after the implementation of the function to be tested. This is the only way to ensure that they can keep up with the continuous changes.

V-model and test-driven development
Figure 1: The V-model and test-driven development

Test-driven Development

The biggest problem with traditional testing in this context is the fact that it is usually done downstream. It is only done when there is something to test. This seems to be trivial at first glance because you would think that if you want to test something, you need a corresponding test object.

Even though the test cases and the relevant data can be derived from the specifications, without an actual test object, the execution seemingly does not produce an analyzable result. However, this dogma does not apply for automated tests. Whereas continual manual testing would be far too expensive in the long run, automated tests can safely be written and executed shortly before the actual implementation, so that they temporarily test only a shell of the future test object. A shell like the one provided by an interface specification, for example.

In this case, the test is a kind of automated specification, the requirements of which are not fulfilled at first (red area in Figure 2). During the implementation stage, the developer can check their code against them, getting immediate feedback whether their work meets the requirements. Once this status is achieved (green area), the state of the source code is assured, and the developer can then restructure it (yellow area). In the case of new requirements or fundamental changes, the tests will fail again (red area again) and must first be passed correctly again (green area again).

More finely granulated structures then result from this general sequence of actions. They result in a very high test coverage that allows for errors to be found early on and simplifies the correction of such errors. Furthermore, interfaces and their implementation are defined more thoroughly than with traditional methods. This is what enables the high level of adaptability of the source code, which is essential for agile software projects.

Der Ablauf der testgetriebenen Arbeitsweise
Figure 2: Sequence of the test-driven method

In addition, one of the most important characteristics of automated tests is their legibility. Basically, they can be understood to be not only a test of the functionality, but also a kind of documentation of the latter. Because of how close they are to the functionality they test, and because of their clear structure, they describe the behavior of the code to be tested in a way that a normal written text would not be able to. And as their result is furthermore completely independent from the test object, they automatically tell the observer if they are no longer up to date.

Behavior-driven development

Component tests are clearly legible only for the group of people who understand the respective programming language and who know the test framework used. Outsiders, such as test managers, project managers or even end users, gain very little from the granularity and the level of detail.

Another weakness becomes evident when you consider the development process as a whole. Tests created with test-driven development are excellent for verifying parts of the software based on their specifications, but not for validating them against the acceptance criteria. But those are the criteria that need to be fulfilled, which is why they should come before the implementation in testing.

This is why the so-called behavior-driven or acceptance test-driven development evolved from the well-known test-driven development. With this type of development, the tests and specifications merge thanks to the use of certain tools and keywords, allowing the test results to be analyzed in plain language. For this purpose, we turn away from describing individual functions towards detailing the expected behavior. This way, the definition of the individual test cases no longer requires background knowledge of the program’s architecture, allowing even non-developers to understand it, and often, it even goes so far as to no longer speak of test cases, but of specifications.

Behavior-driven development and test-driven development are not to be understood to be rivals, however. The former primarily serves to validate the software and test the system integration, while the latter serves to verify and thus ensure the functional correctness. Consequently, it makes sense to combine the two.

Effort

Due to the fact that developers are expected to write the tests in addition to the actual product development, test-driven development involves greater effort than traditional methods at first. An adequate infrastructure needs to be established, the use of additional tools needs to be learned, and additional code that does not immediately result in usable features needs to be written. This may be discouraging at first, but in fact it only illustrates the effort that will otherwise be required much later over the course of the project. Instead of the effort involved in long testing and bug-fixing phases near the end of the project, a large part of the quality assurance is prepared at a time when the project team still has quite a lot of room to maneuver. The work required decreases as the project progresses because the infrastructure usually has to be established only once, and then only needs to be adjusted here and there.

Effort of test-driven development
Figure 3: When is TDD profitable?

Conclusion

Using the methods presented in this post constitutes a significant break with the traditional methods of software development in some ways, but it facilitates a kind of quality assurance and documentation that is difficult to achieve otherwise. Basically, the software itself reveals its current status without the need to maintain external resources that can quickly become obsolete. By combining the different approaches, you can ensure a high level of quality and a comprehensive understanding of the project across all levels of abstraction.

This post was written by: