Successfully adopting TDD in your team
Learn the enemies and the keys to implement TDD in your team.
Test-Driven Development is a game-changing methodology for any software project with a life span more extensive than one year; unfortunately, many developers and companies fail to embrace it. In the first instance, it might seem a problem of developer skills and lack of self-discipline, but it is not. You cannot enforce TDD on your team, it does not work this way, but if you overcome the key challenges, you will successfully adopt it.
The story of two teams
Once upon a time ago, two teams worked in the same project but different repositories: the Antelope team and the Beaver team. The Antelope team decided to start with TDD from the first day; the beginning was hard, but instead of enforcing what they thought that TDD was, they kept learning and looking for better ways to improve their tests. They bend TDD until it fitted in their needs. The Beaver team also started with TDD, it also was hard, but they tried to be strict and follow what they understood that TDD was. Although the team had a strong will and tried and tried embracing TDD, their tests became almost useless, and their TDD almost unexisting.
Two years later, a new member without TDD experience and equally skilled entered into each team. They were Alice and Bob. Alice joined the Antelope team; Bob joined the Beaver team. One month later, Alice was successfully practicing TDD. Six months later, Bob was still struggling with tests and failing to adopt TDD.
TDD is a full team commitment
When a team fails to implement TDD, it starts with individual pitfalls, but it ends with a full team failure. The whole team must practice TDD during all times without exceptions. If one, or more than one, members fail to practice TDD at all times, the entire team will eventually stop doing TDD.
According to the Agile Alliance (see here), some of the most common reasons a team member fails to apply TDD are: not running tests frequently and writing too many tests at once. It also states that if there is a partial adoption, the team will fail to adopt TDD.
If there is a partial adoption of TDD, the whole team will fail to adopt TDD.
TDD is a full team commitment. If one or more group members fail to practice TDD correctly, not only will they affect the work they do, but they will also affect the work their peers do. When these members fail to implement TDD because of any of the aforesaid individual reasons, they will create obstacles that will prevent their peers from following TDD practices. It will start a chain of successes that will damage TDD on that team severely. But why?
It is the source code that guides developers.
Did you have stopped to think about why Alice learned TDD in just one month? Did you have considered that it was because their colleagues helped her? Or there was another reason?
The source code contains more than the implementation of your product; it contains millions of microdecisions from the developers that have contributed to it. Each decision represents one strategy and one direction; the sum of all defines the least resistance path.
When a new developer joins a team, the first thing that he has to do is to read the code. He needs to understand how it works and how he can introduce or fix new features. And the primary technique used, for both new and old developers, is the copy-paste. When any developer needs to implement a new feature, the first thing he does is look for similar code and copy-paste it.
Imagine that you have to implement a new feature and because you are doing TDD, you have to start with a test. Which would be your first step if you have a large codebase? Without any doubt, you will look for an existing test similar to what you need, and you will copy-paste it. If your tests are clean, it will become easy: with few tweaks, it will work quickly. If you spend a little more time creating common abstractions in those tests, it will keep being easy next time.
The source code is the most valuable member of your team.
Why was Alice able to embrace TDD in just one month? Because the source code assisted her. The source code has more value than the implementation of your product: it is the most valuable member of your team. And you should invest in it consequently.
Manual testing: Enemy number 1.
We all agree that manual testing is one of the more expensive and risky practices. From the QA point of view, it is costly to manually run all the test each time you release the product; it usually translates in reducing the number of tests, taking some risks, and do only the minimum number of tests. That is why most QA teams have an objective to automatize those tests. They are the only guarantee to keep the price low while adding more tests. Do a test manually, and you will have to repeat in the next release; write an automatic test, and you will able to rerun it in each release almost for free.
But manual testing goes beyond QA: developers are continuously testing your product while they build it. Without TDD and automatic testing for developers, each time that the developer makes a change in the code, he restarts the application, navigates screens, inputs data, and checks results. It takes time, and he has to repeat these steps many times. It is expensive, but the problem is even more severe.
According to TDD mainstream, if developers test manually while coding, the quality of the tests plummets. There are two reasons: first, automatic tests are pointless for them because they know that the code works, but second, and more importantly, you cannot know if tests work correctly. Making the code pass a failing test is how we check that tests work correctly.
If you want to succeed with TDD, remove all needs for manual testing.
And this drop in quality for tests will unquestionably affect your team practices. Manual testing is the greatest enemy of TDD. Once we start doing manual testing, we kill TDD. If you want to succeed with TDD, remove all needs for manual testing.
Unit Tests: Enemy number 2.
We, as an industry, have a problem: Unit Tests. According to the traditional QA semantics, a Unit Test is a test whose failure implicates one and only one unit. For example, if we are designing a car that requires a feature to light the street turning on a switch, we will need at least four units: a battery, a switch, a lamp, and a light bulb. And four unit tests, one to check that the battery provides energy, another one to test that the switch works, another one that shows that the lamp can pass electricity to the lightbulb, and finally the last one that shows that the lightbulb works.
The problem is that none of these tests gives you confidence that the feature will work. What if the battery has the wrong voltage? What if the lightbulb does not fit the battery? You cannot ensure that unless you start integrating units. And if you do that, you are breaking the rule that one failure implicates one and only one unit. But, if the developer only creates unit tests, how the developer knows that the feature is working correctly?
With unit tests, the developer needs to test the application manually. Only running the application will know that all units work together correctly. And this will break TDD.
What is the alternative? Is it making integrated tests only? Oh no. That is not the solution. Fully integrated tests are necessary for QA and know that everything works correctly, but they are too slow for the developer. With TDD, the developer runs tens, or hundreds, of times the tests in each step; that creates the virtuous circle that gives so much value to TDD. But fully integrated tests are slow; instead of executing one or less milliseconds, each test takes seconds or even minutes. They take too slow for TDD.
The alternative is developer tests. Ward Cunninghan, the precursor of Test Runners, XP, and TDD, explains it in his wiki:
A DeveloperTest is the correct name for what the industry generally calls a UnitTest. […] TestDrivenDevelopment produces DeveloperTests. The failure of a test case implicates only the developer’s most recent edit. This implies that developers don’t need to use MockObjects to split all their code up into testable units. And it implies a developer may always avoid debugging by reverting that last edit.
With developer tests, the developer does not need to run the application manually.
What is a developer test? If we return to the car factory example and the light feature, the developer test would be: get the headlamp, a light bulb, a battery, a light switch, interconnect them. Turn the switch on. The test passes if the light bulb emits light. It runs fast, in the order of milliseconds, and now the developer does not need to run the application manually to know that everything works together.
Action 1: Invests in your tests
To invest in your tests is the most obvious task that you have to perform to achieve TDD successfully. We are talking of TDD, Test-Driven Development, your tests drive your development, literally. The better your tests are, the better your product is.
On the one hand, you need to create rich tests because you need to avoid manual testing at all costs. When the developer needs to execute the application manually to check that he is advancing correctly, you have lost the game of TDD. Make sure that your developers are creating tests good enough to avoid manual testing.
On the other hand, you need to maintain your testing code. Your testing code should have the same or better quality than the production code. Do not hesitate to create abstractions, tools, and many components as you need to make your tests easy to maintain and easy to read.
You have to create the tools to make testing smooth.
If the project has been running for a while without proper TDD, you will discover that creating appropriate tests is hard. You will face two problems: first, you will have code hard to test, and second, you will have few or no abstractions to help write rich tests. If you do not know how to start, focus on tests. Get the current feature and one similar recent feature, create a decent developer tests for each one. Not just the code of the test, but also the code of the tools for testing both; you have to create the tools to make testing smooth.
Action 2: Pair TDD and ATDD
When we talk about TDD, we talk about how the developer creates tests that drive development, but with agile, TDD goes beyond the developer.
TDD is all in one practice. When we say that we write tests before development, we do not focus only on developer tests; we also talk about tests that QA and Product do. And this is a game-changer.
The first problem of decoupling QA from the developer is that the developer is never sure about what to expect. He can decide to implement fewer cases and leave QA to reject the code if some case is missing, or he can overwork and make sure that QA will find nothing, but spending far more resources than required.
The second problem is the lack of communication and common language between all roles. When Product has one idea, the developer understands another thing, and the QA has a different final interpretation. All this lack of understanding slows down all the development; roles work based on guesses instead of targets.
When QA is after Developers, it creates a black market of defects.
The third problem is the most severe one: it creates a black market of defects. It is reasonable to think that the developer has an incentive to finish sooner to implement a feature. And, if there is a QA team after him, a QA team that will always find something, he can relax some cases and deliver sooner the feature. It will raise defects, but he will finish sooner. Similar happens to QA; if QA finds no bugs, which value have? So they value positively finding bugs. Both incentives create a feedback loop that amplifies the number of defects found and increases the project’s cost.
All three problems have one solution: ATDD: Acceptance Test-Driven Development — BDD is one implementation of ATDD — . ATDD assumes that there is a broader TDD loop that is driven by Acceptance Tests.
Acceptance Tests are tests written in a natural language, like English. One of the most famous variants is the Gherkin syntax (https://cucumber.io/docs/gherkin/). It is essential because those tests have two properties: first, they are executed automatically by a program; second, they are understood and discussed by every member of your organization without ambiguities. They have high value, and they are the perfect documentation for all functionalities that your product has.
There are two levels in tests: Acceptance tests and Developer tests. Both test the same thing, but they use different languages and different degrees of integration.
With ATDD, Product can write the first version of a feature’s primary cases, developers can start to implement them, and QA can begin reviewing the cases and adding tests to avoid defects before appearing. Three roles are on the same agenda, and no defects should appear once the developer has finished the development.
Write Acceptance tests before the development.
ATDD has one more advantage: acceptance tests and developer tests are very similar. The most significant difference is the language, natural language vs. coding language, and the degree of integration. With acceptance tests, the developer knows precisely which tests write, and they probably would have excellent quality. In some projects, in some cases, the developers do not need to use developer tests in all cases, and some teams, developers also implements the automation of acceptance tests.
This article focus on the main hazards and main actions that we need to implement TDD successfully. The main reason why TDD adoption fails is that we test the application manually; it can be because of a lack of expertise on TDD, but also because we were unable to create a good test base. Unit tests are one of the leading causes of TDD’s failure; focus on developer tests instead.
If you want to convert your team to TDD, focus on tests: you should have the best quality possible in the test code. But also embrace ATDD, or BDD; if you are planning to create those tests anyway, better start with them so the whole team can work more effectively.
This article does not explain why TDD is good, or how to write tests; it assumes that you are already familiar with the concepts. If you need to know more, please write a comment.