TDD is a software development approach in which a test is written before writing the code. When TDD is properly set up, it can bring numerous advantages and become a cost-saver, providing true value to the business. When TDD is not properly set up or without understanding how it should be used, it can be a waste of time and money. Quality comes not from inspection but from the improvement of the production process
Did you know:
We spend 10 times more on reading the code than writing because to write a new one you should understand what the old one does (Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship.
620 million developer hours a year are wasted on debugging software failures, at a cost of roughly $61 billion (anew study by Cambridge Judge Business School).
Software engineers spend an average of 13 hours to fix a single software failure (a new study by Cambridge Judge Business School).
The cost to fix bugs found during the testing phase could be 15x more than the cost of fixing those found during implementation/design (Capers Jones, Applied Software Measurement: Global Analysis of Productivity and Quality).
To address these challenges Test-Driven Development (TDD) comes into play. TDD is a software development approach in which a test is written before writing the code. Once the new code passes the test, it is refactored to acceptable standards. The following sequence of steps is generally followed:
Add a test
Run all tests, and see if the new one fails
Write some code
Repeat steps 1–5
This practice of writing failing tests, known as the ‘red-green-refactor’ cycle, focuses on the creation of operations that are callable and testable.
Each cycle should be very short.
The benefits of TDD include:
getting the fastest feedback
creating a detailed living specification
fewer bugs (we find them before deployment)
enforcing writing SOLID code
To get all of these benefits you will need to have your team members test-infected in a good way, so everyone should write tests. They will need to be retrained, and you will need to cultivate the culture in which the whole team is responsible for the test automation and quality in general. Before you start requiring tests to be written before implementation, you will need to make sure your teams and the application’s code is ready for that.
First, you need to change your focus to isolated unit tests and isolated component integration tests. GUI and integration tests are slow, and they do not put pressure on design. They are expensive to write and maintain, and they are fragile. You do not want to put a testing pyramid here—you know what I am talking about.
Test-Driven Development implementation steps:
Make your code testable. One of the biggest challenges with implementing TDD is legacy code with 0 unit tests. To address this challenge you will go forward with baby steps. No one is going to jump from 0 unit tests to TDD with 100 percent coverage. Start with refactoring and making your code testable for applications that have larger value (build a priority matrix), and cover them by unit tests first.
Revisit what you have, and refactor the existing code into testable and tested code.
To refactor existing code:
Modify code to make it SOLID (SOLID principles).
Extract all non-testable code (private methods, external dependencies calls) into wrapper-classes.
Get rid of static methods and variables; isolate code that is not unit-testable by nature (like external APIs).
Make the code loosely-coupled. Think about how to break down what you have into a set of simple building blocks so that each does one very simple thing.
Convert all impure methods into pure methods so they do not use anything that’s not given as arguments to compute nor does it modify anything it is given, which makes it safe. You can call it a thousand times with the confidence that nothing you do not expect will be changed because of it.
Organize workshops on TDD and Unit Testing. You will need to find a TDD guru (it could be an external coach) and give some workshops on that and unit testing in general to your teams.
Workshops should cover the following basic topics:
what unit tests are and how they compare to other types of tests
patterns for writing effective tests
isolated tests using stubs and mocks
manual mocking and auto-mocking
Workshops should cover the following advanced topics:
dealing with large objects and testing asynchronous code
organizing tests for a large system
unit-testing best practices
TDD in legacy code
breaking dependencies in legacy code
strategies for increasing unit test coverage
using unit tests in automated builds
You can also have several workshops on Behavior Driven Development (BDD). These will help to address challenges without having to understand what to cover by automated tests. Then we can write requirements (acceptance criteria) with examples for the project using Domain Specific Language (DSL), which will serve as living documentation after automating those scenarios. This type of documentation is always up-to-date, and it is a single source of truth when it comes to finding out the system behavior. You will only cover by automated tests the behavior of the system (application under test) that matters most from the business point of view. Be pragmatic, and don't chase the magical 100 percent coverage.
Organize pair-programming sessions with those who know TDD. You will need to pair those who have never worked in such an environment with those who have been there and know how to do it right.
If you say “It will slow us down, we can’t waste our time on it,” my response would be “That slowness is an illusion.” Writing tests first usually means the code quality will be better, and there will be less need for rework and churn later, which equals fewer defects. Eventually, it will pay off.
One of the key benefits of pair programming is knowledge sharing among team members. The more knowledgeable members should play the role of a coach for the beginners. This way everyone can get practice in designing, implementing, and executing unit tests while under supervision.
Set up a special sprint dedicated to getting rid of the technical debt, and improve unit-tests coverage so that everyone can practice what they learned during workshops.
Without practice, everything that was learned will evaporate. During those special sprints, developers will practice:
writing unit tests
refactoring code to make it more testable
mocking different kinds dependencies
including tests in automated builds
using TDD to develop new code
Build a TDD Excellence team within the company to help other teams follow this approach. This team will act as a consultant company within the organization, help everyone stick to the TDD best practices, and roll out this across all of the teams.
In a couple of months after writing a decent number of tests, you will be able to follow TDD with a couple of teams. Then you will be able to roll it out across the organization and use those teams as a role model. Remember that this process should not be enforced—it should come naturally by cultivating a culture of writing unit tests for new features within the sprint and making it a part of the Definition of Done. You can set quality gates on the unit test coverage for newly added functionality (for example, using SonarQube) to visualize the progress in expanding the coverage.
Quality comes not from inspection but from the improvement of the production process. The quality is there or not by the time it's inspected. When TDD is properly set up, it can bring numerous advantages and become a cost-saver, providing true value to the business. When TDD is not properly set up or without understanding how it should be used, it can be a waste of time and money.