Test-Driven Development (TDD) is an evolutionary approach to conducting a development process which combines test-first development, where the developer writes automated unit tests before he writes just enough code to fulfill those early tests, which is then refactored to an acceptable standard.
TDD ensures that each part of the code is thoroughly tested by a dedicated set of automated unit tests, which leads (in theory) to the creation of resilient, modular, bug-free and flexible code while making the design clear and simple.
The life cycle of Test-Driven Development (TDD)
The steps of a test-driven development cycle are:
Write Test – All new code in TDD starts with a test, which fails as it is created before the code is implemented. To use early testing, the developer must have a clear understanding of the requirements that he is supposed to implement. In Agile development, this knowledge is provided in user stories and their acceptance criteria.
Common practices for writing tests:
Each test should be free from dependencies.
Split the tests into smaller tests as much as you can.
Ensure that your tests are efficient, as they serve a higher process of the development cycle like CI/CD.
Ensure that each test adds real value, don’t add them just because you were asked to.
Your tests should cover both positive and negative scenarios.
Each test should focus only on the results necessary to validate it.
Review your tests with other team members to gain their opinion.
Execute test – Tests are executed and expected to fail as the code is not written yet. The main reason to run the tests is to ensure the tests work correctly and are not passing by mistake.
Write code – once the tests are written, it’s time to write the code that clears the tests.
Re-execution of tests – To ensure that the new code meets all required specifications it has to pass the tests.
Refactoring –The refactoring process focuses on stabilizing and improving the code. Refactoring involves the following aspects:
Removing code duplication that may be caused by the addition of new functionality.
Increasing code efficiency without affecting its functionality.
Performance improvements that improve the overall solution.
Final approval – To ensure that the refactoring process does not impact any requirement, re-execute the tests to ensure it passes.
How testers fit in
TDD does not talk about the testing which testers are supposed to do. TDD focuses on the testing which programmers do in the first layer of the Agile test pyramid (unit level). This raises the question of what the testers’ part is in an approach that is under the programmer's responsibility.
Although it’s the programmer’s job, the testers can still have a major contribution to the process. Testers can share their experience and knowledge in the testing field to determine test cases that are beyond the eye of programmers, for example:
Testers can determine the overall test strategy for the whole feature and categorize which tests should be conducted at each test level.
Testers help the team determine the overall test strategy related to manual vs automated test efforts.
Testers collaborate with programmers to ensure that the tests provide real coverage based on advanced test techniques such as equivalence class partitioning, boundary value analysis, etc.
Testers can review test execution results and share their feedback.
For this to happen, both testers and programmers should embrace collaboration and step into each other shoes to gain an understanding of the challenges. For example:
Programmers should have equal ownership of the overall quality of the product, they cannot rely on the testers for finding bugs.
Testers must evolve and upgrade their set of skill.
Testers should learn the technical jargon of programmers to create a shared language.
Programmers should step into testers’ shoes and learn how to use test techniques that improve the overall quality of the unit tests.
Benefits of Test-Driven Development
Agile development teams have benefited a lot from using test-driven development. Here are just a few reasons to explain how:
It increases the quality of the code – TDD allows developers to increase the quality of their code by making better design decisions. Developers can write and test smaller parts of the code, which makes the code simpler to understand.
It reduces risk and cost – By ensuring that all parts of the code are tested based on user requirements, developers have confidence that logical and syntax errors will be found at the earliest phases of the development process, which reduces potential risk further in the development process.
A better understanding of the code– By writing tests early, developers can better understand how the code is used by the user and how it influences and interacts with other components in the system.
It saves time – It takes time to write tests, but once they are written developers can use them over and over again, saving a lot of time compared to running the same tests manually.
Reduced regression effort – TDD creates an automated regression test suite that the team can use as part of their regression effort.
It increases confidence – If you refactor code, you might break it. Having a suite of automated tests that catches such breaks, increases the developers’ confidence to refactor code, as bugs are found and fixed prior to the code’s release.
Documentation – Unit tests can become a great source of information about the code with specific details about the test and what it intends to validate.
Reduce debugging time – Once developers have created test suites for their code, they will not spend hours debugging as they can use the tests to find errors.
Constant feedback – Running an automated test suite provides the team constant feedback that each part tested is still working and that there are no bugs that weren’t there prior to the change.
The limitations of TDD
Though this method adds real value to the development process, it still has limitations that must be considered before you decide to implement it in your own environment.
TDD does not provide a framework to handle specific test types such as GUI, UX, etc. Furthermore, TDD is mainly used at the unit level, as it can be hard to write tests beyond the unit testing level.
TDD is a great method for new code. However, TDD can become very difficult when applied to existing legacy code.
A false sense of quality. As the test suite contains more passing tests, developers may start to gain a false sense of quality and reduce the effort of conducting different test activities in the advanced test levels (integration, system, etc.).
Maintenance overhead. TDD contains hundreds and even thousands of test cases. As a result, they become part of the maintenance overhead.
TDD demands a specific mindset where developers take ownership of quality. This is something that should be embraced by the whole team.
As with any other automated tests, unit testing can be taken to an extreme level. It is so important to find the correct balance between the number of written tests and the actual coverage they provide.
How to determine the quality of your TDD process?
To determine how good a TDD process really is, we can measure the number of bugs found by the team once they receive a build that passed all the TDD and CI tests and fits one of these criteria:
Bugs that should have been caught if the team was spending more time and effort on creating of the TDD tests.
Bugs that are a result of requirements that were missed or misunderstood.
Bugs that are a result of code refactoring that wasn’t covered with proper tests.
If your TDD process is good enough, the number of bugs found matching the criteria above should be reduced each sprint as the team increase their test coverage and start mastering TDD.