Updated: Jan 26
Test-Driven Development (TDD) is an evolutionary approach to conducting a development process that combines test-first development. The developer writes automated unit tests before writing just enough code to fulfill those early tests, 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 creating resilient, modular, bug-free, and flexible code while making the design clear and straightforward.
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 clearly understand the requirements to implement. In Agile development, this knowledge is provided in user stories and their acceptance criteria.
Standard 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 development cycle process 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 must pass the tests.
Refactoring –The refactoring process focuses on stabilizing and improving the code. Refactoring involves the following aspects:
Removing code duplication that the addition of new functionality may cause.
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 that testers are supposed to do. TDD focuses on the testing that programmers do in the first layer of the Agile test pyramid (unit level). This raises the question of the testers’ part in an approach under the programmer's responsibility.
Although it’s the programmer’s job, the testers can still significantly contribute 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 full 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 actual 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 skills.
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 improve the quality of their code by making better design decisions. Developers can write and test smaller parts of the code, making 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 the potential risk further in the development process.
A better understanding of the code– By writing tests early, developers can better understand how the user uses the code and how it influences and interacts with other components in the system.
It saves time – It takes time to write tests, but developers can use them repeatedly once they are written, 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. A suite of automated tests that catch such breaks increases the developers’ confidence to refactor code, as bugs are found and fixed before 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 regular feedback that each part tested is still working and that there are no bugs that weren’t there before 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 implementing it in your 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 an excellent 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 the whole team should embrace.
Unit testing can be taken to an extreme level as with any other automated test. It is essential to find the correct balance between written tests and their actual coverage.
How to determine the quality of your TDD process?
To determine how good a TDD process 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 had spent more time and effort creating the TDD tests.
Bugs are a result of requirements that were missed or misunderstood.
Bugs resulting from 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.