Test Driven Development (TDD)

How to effectively apply TDD in Software Development

Test Driven Development or TDD is a rather controversial practice, as some argue that it is essential, while others claim that it requires too much effort and time, and that it doesn't pays off.

 

If you have experience with TDD, you probably have your own opinion about its value and you will find many topics here to expand your knowledge and skills. However, if you are not familiar with the practice, this will be your starting guide.

 

We've put together some useful content to help you dive into TDD and learn best practices to get the most out of it in your projects.

 

Is TDD really necessary? What does it bring to my projects? Does it work as a software design methodology? How can I improve my TDD skills? Regardless of your level of experience, here you will find answers to improve your testing skills.

 


 

Why should I use TDD?

 

At Codurance, we believe that TDD is a practice that, when applied correctly, adds agility to software development processes, contributes to building more reliable and flexible code, and promotes scalable quality solutions thanks to the automation of processes. In this section, we explore the reasons why we consider TDD will help you enhance the quality of your code.

 

 

What is TDD and how does it work?

 

TDD, or Test-Driven Development, is a software development methodology that emphasises writing automated tests before writing the code. Broadly speaking, it involves formulating a hypothesis about a behaviour of the system we want to develop and then corroborating that hypothesis or behaviour with the code being written. Always trying to ensure that the code validating the hypothesis is the minimal possible to validate it and thus avoid falling into over-engineering.

 

The TDD process consists of iterations over a three-stage cycle: Red-Green-Refactor. In each iteration, a new hypothesis about the behaviour we want implement in the system is addressed. Let's see what each stage of a cycle involves:

 

Red: Write an automated test for a new feature that initially fails because the feature has not yet been implemented. This step ensures that the test is valid, i.e. it fails because the desired behaviour is not implemented, and needs the new functionality to pass.

 

Green: Write the minimum code necessary for the test to pass. This step focuses on making the test pass as quickly as possible, without worrying about code perfection.

 

Refactor: Once the test is passed, the code is adjusted to improve its structure and quality, while the tests continue to work. This may include removing redundancies, improving readability, and applying software design principles. 

 

The 3 Laws of TDD

 

1. You cannot write production code without first running a failed test.
2. You cannot write more than a single unit test, with enough code to make it fail (compile error is failure).
3. You can only write production code to make the single failed test pass.

 

 

Advantages of using TDD

 

TDD is a powerful methodology for developing high-quality software in an iterative and systematic way, fostering better software design and reducing errors from the early stages of development.

 

Advantages of using TDD

 

Benefits of TDD at a Technical Level

  • Enhances software quality. It increases the reliability of the code and makes it easier and more reliable to make changes to the system.
  • Promotes agile development. Although TDD may initially seem to slow down the development process, in the long run, it can make it more efficient by reducing the time spent on debugging.
  • Contributes to a better, more comprehensive, and maintainable software design, as long as you know what a good design looks like.
  • Provides regression coverage (safety net).
  • Aligns with a QA culture since the code written is intended to be tested.
  • Adds structure to the development process.
  • Reduces feedback loops, allowing our code to be deployed more quickly and safely.

 

Benefits of TDD for Business

  • Helps to reduce long-term costs associated with continuous debugging.
  • Reduces the risks associated with software maintenance and scalability. Well-designed and tested code is easier to maintain and scale.
  • Increases customer satisfaction and loyalty through higher product quality.
  • Accelerates the Time to Market of new releases and products by facilitating continuous integration and delivery (CI/CD), enabling more frequent deliveries.
  • Elevates the technical level of the team and contributes to talent retention.
  • Lowers entry barriers for new developers, as each test describes the expected behaviour (live documentation). 

 

Common barriers to TDD 

 

The implementation of Test Driven Development (TDD) offers numerous benefits at both the technical and business levels, but it also poses several challenges that organisations and development teams must overcome to fully harness its potential. These challenges include:

 

Initial learning curve: For teams unfamiliar with TDD, learning to write effective tests before code goes into production requires a mindset change and the acquisition of new skills and practices.

 

Initial time and costs: Writing tests before functional code may seem to slow down progress at first, especially in the early stages of a project when the team is still adapting to this methodology.

 

Cultural change: TDD is not just a development technique; it is a philosophy that requires a cultural change within the development team. Adopting TDD means changing the mindset from "writing code first" to "test-first thinking".

 

Test maintenance: As the project grows, so does the test suite. Keeping these tests up to date with changes in code and requirements can become a challenging task if you don't have the necessary skills.

 

Integration with existing systems: Applying TDD to legacy systems or ongoing projects can be tricky, as they may not be designed with a test-first mindset.

 

To overcome these challenges, organisations may need to invest in training and mentoring, promote a culture that values software quality and continuous improvement, and gradually adapt their development processes and practices to TDD principles.

 

 

TDD and Software Craftsmanship

 

Test Driven Development (TDD) and the Software Craftsmanship movement are intrinsically related, as both focus on improving software quality and promoting development practices that add real value to projects. In both TDD and Software Craftsmanship there is a commitment to continuous improvement.

 

Software Craftsmanship approaches software development as a long-term career, in which development teams must continuously learn and hone their skills. TDD is one of the essential practices in a software craftsman's arsenal, as it promotes a disciplined, incremental approach to software development, ensuring complete and reliable features through testing against possible regression errors.

 

In other words, TDD provides a practical framework that helps to achieve the ideals of quality, continuous improvement and professionalism that Software Craftsmanship promotes. Together, they foster a development culture where technical excellence is paramount, guiding professionals towards creating software that not only meets functional requirements, but is also sustainable, scalable and easy to use.

 

 

How does TDD improve software quality?

 

Test Driven Development (TDD) improves software quality through several key mechanisms. These principles not only affect code quality from a technical perspective, but also influence the software development process, ensuring that the end product is more robust, reliable and maintainable. 

 

By applying a test-first approach, TDD reduces the likelihood of introducing defects into the code from start. This is because the success criteria for features is defined before they are implemented, which reduces ambiguity and improves the accuracy of software requirements. 

 

Tests serve as a safety net that allows the development team to make changes or refactorings to the code with the confidence that any regressions or bugs introduced will be quickly caught by the existing tests.

 

TDD encourages an iterative and incremental approach, where features are built and tested in small increments. This ensures that each part of the system is implemented and tested correctly before moving on, reducing the complexity and risks associated with large block development.

 

Tests also act as a form of live documentation that describes how the software is supposed to behave. This is especially useful both for system maintenance and for on-boarding new developers, as they can quickly understand the intentions of the code by reviewing the tests.

 

 

Building a case for TDD 

 

Convincing your company to adopt TDD involves making a strong case that highlights the long-term benefits of this methodology, as well as addressing potential concerns about the initial investment in time and resources. Here are some key points you can use to build your case. 

 

1. Highlight the business benefits. Highlight how the initial investment in TDD can lead to long-term savings by reducing maintenance costs through early detection of errors. Also emphasise that TDD can give the company a competitive advantage by enabling a faster response to market demands thanks to more modular and flexible code that makes it easier to implement changes and new functionalities.

 

2. Address concerns about time and cost. Acknowledge that adopting TDD will require time and possibly training for the team, but argue that these are investments in the quality and sustainability of the software. The result will be a more stable and reliable product and will improve customer satisfaction and the company's reputation.

 

3. Showcase Case Studies. Look for examples of other companies, especially those in the same industry or of similar size, that have successfully adopted TDD. Presenting tangible results and testimonials can be very convincing.

 

4. Provide training opportunities and resources. Suggest resources for team learning, such as courses or pair programming sessions with TDD experts. Showing that there is a clear plan for skills development can ease concerns about the learning curve.

 

5. Develop a phased implementation plan. Make a proposal to adopt TDD gradually, starting with a pilot project or a small team. This allows the benefits of TDD to be evaluated with minimal risk and provides an opportunity to learn and make adjustments before a wider deployment.

 

6. Measure and share progress. Present specific metrics to assess the impact of TDD, such as reducing the number of bugs reported, improving development time for new features, or decreasing debugging time. Commit to measuring and sharing these results regularly to demonstrate the value of TDD.

 


 

How to apply TDD?

 

As explained in the previous section, Test Driven Development (TDD) involves the adoption of a cyclical and systematic approach to software development, where testing guides the writing of code. It also requires a change in the team's mindset and culture.

 

Once we have considered its benefits and challenges and want to start applying it, the question arises: where to begin? The immediate answer is that getting started requires a combination of training, education and lots of practice.

 

In this section we look at TDD best practices, some topics of discussion, tools that can help with implementation and anti-patterns to avoid.

 

 

TDD Best Practices

 

Successful implementation of Test Driven Development (TDD) depends on following good practices to ensure the effectiveness of the process and the quality of the software. Here is a list of recommended best practices for TDD.

 

TDD Best Practices for developers

 

Small and focused tests. Each test should focus on a single aspect or feature. This makes tests easier to write, understand and maintain.

 

Clarity and readability. Tests should be clear and readable, acting as living documentation of the system. They should explain the purpose and intended use of the code to ensure its common understanding and long-term maintainability.

 

Test naming. Test names should be descriptive and reflect what they are testing. This can help others understand the purpose of the test and what aspect of the code is being validated.

 

Refactoring. Refactoring does not just apply to production code; tests should also be refactored to improve their clarity and efficiency.

 

High Frequency. Tests should be run as often as possible, ideally automatically through continuous integration, to provide rapid feedback on code changes.

 

Independent tests. Each test must be independent and not rely on other tests. This guarantees that they can be run in any order and that the failure of one test does not affect the others. Thus, it is convenient to run all tests when a new behaviour is added (in the Green phase) because passing (green) the last test does not mean that there is no error elsewhere. This way you make sure that no regression error has been introduced.

 

Simplicity. Tests must be simple and avoid containing conditional logic or loops, as this can introduce errors into the tests themselves and make them more difficult to understand and maintain.

 

Discipline. Stay true to the TDD (Red, Green, Refactor) cycle and resist the temptation to skip steps or write code without a previous failed test. This ensures that the TDD process remains effective.

 

Pair Programming. Pairing helps you to get immediate feedback on your tests and their behaviour. By doing pair programming you can improve your skills thanks to the positive and constructive exchange of ideas.

 

 

TDD and Software Design

 

The relationship between TDD and software design has become one of the most debated issues among industry professionals. Who better to reflect on this topic than Sandro Mancuso, author of The Software Craftsman, pioneer in Software Craftsmanship, and experienced developer with more than 25 years in the industry.

 

In the following articles, Mancuso raises questions that any developer (beginner or experienced) may face in their career, and also suggests scenarios and concerns that will invite you to draw your own conclusions. 

 

Does TDD really lead to good design?

 

"TDD is not a design tool. It’s a software development workflow that has prompts for code improvement in its lifecycle", states Sandro and explains styles and guidelines to make this relationship successful. Read more

 

Should we always use TDD to design?

 

"The question is not if we should or should not design up front, but when. Design happens at many different levels (architecture, macro and micro) and has different degrees of complexity"explains Sandro and describes the different design approaches along with tips on how to apply them. Read more

 

TDD is NOT (just) about you

 

"A software project is not about any single person. So, if you still think you don’t need TDD or automated tests, think about all the other developers. Think about the people that will maintain that code once you are gone", Sandro reflects, and addresses the excessive customisation that exists in the world of software development. Read more

 

 

Styles of TDD: Chicago and London Schools

 

Among the most widely practised styles of TDD are the Chicago School and the London School. Each has its own particularities, focusing on different aspects of software development and how testing should be conceived.

 

These styles were popularised by a series of videos in which Bob Martin (Chicago) and Sandro Mancuso (London) compare the development of an application using the two approaches.

 

Chicago School. Also known as classic or traditional style, it follows an inside-out approach and focuses on testing the expected behaviours of the software from the inner, or domain, parts of the system to the outer layers where the interaction with the users takes place.

  • Advantages: It is more straightforward and can be easy to implement for new TDD practitioners. Focuses on getting the code right from the start, prioritising simplicity in design.
  • Challenges: Can lead to less UI-centric and more implementation-centric design, sometimes resulting in less modular and more tightly coupled code.

London School. Also known as the Mockist style, proposes an outside-in development and focuses on the design and architecture from the outer layers of the application to the inner layers, which represent the software domain. In this type of development, mocks and stubs are used to simulate the interactions between different units of code, focusing on how the pieces of the system interact with each other, rather than their individual behaviours.

 

In outside-in there is usually a double cycle: a first acceptance test with its cycle, and then the subsequent unit tests that are created until the acceptance test is passed (each with its own red-green-refactor cycle).

  • Advantages: Promotes modular and decoupled design, and is very useful in complex systems where interaction between components is a critical aspect of the design.
  • Challenges: Requires a deeper understanding of software design concepts and may lead to a higher learning curve.

Both schools offer valuable insights on how to approach software development to prioritise quality, sustainability and efficiency of the development process.

 

The choice between the Chicago School and the London School often depends on the type of project, the preferences of the development team and the specific software quality and design objectives.

 

 

Infrastructure: TDD and pipelines

 

The relationship between TDD and CI/CD pipelines is fundamental to modern software development. TDD ensures that code is well tested from the start, while CI/CD pipelines automate the execution of these tests, providing a safety net that facilitates fast and secure integration and delivery of changes.

 

Together, they make a powerful combination that improves software quality, reduces the risks associated with deployments and increases the speed of delivery of new functionality.

 

 

Tools to facilitate implementation

 

To facilitate TDD application, it is essential to have the right set of tools in place, from the developer's local environment to the continuous integration and delivery in the pipeline.

 

Tooling for development team

 

Their purpose is to facilitate writing, running and debugging tests.

  • Unit testing frameworks: Tools such as JUnit (Java), pytest (Python), and Jest or Mocha (JavaScript) allow the development team to write and execute unit tests efficiently. These frameworks offer a wide range of functionality for defining test cases, assertions and mocks.

  • IDEs and plugins: Integrated development environments (IDEs) such as IntelliJ IDEA, Visual Studio Code, and PyCharm, among others, often come with plugins or extensions that facilitate TDD. These can include integration with test frameworks, one-click test execution, and visualisation of test results directly in the IDE.

  • Code coverage tools: Tools such as JaCoCo (Java), Coverage.py (Python), and Istanbul (JavaScript) help measure test coverage, providing visibility into which parts of the code are not being tested.

 

Pipeline tooling

 

These tools automate the execution of tests and other code quality-related tasks as part of continuous integration and delivery (CI/CD) pipelines.

  • CI/CD servers: Jenkins, GitLab CI, GitHub Actions, and CircleCI are platforms that can be configured to build CI/CD pipelines. They allow unit, integration, and other tests to be run automatically each time new code is pushed to the repository.

  • Code quality analysis tools: SonarQube and Codacy offer static code analysis that can be integrated into the pipeline, helping to identify quality issues, security vulnerabilities and technical debt.

 

SaaS Services

 

Cloud-based services (Software as a Service, SaaS) offer a scalable, managed infrastructure for continuous integration and delivery, as well as for collaborative software development.

  • CI/CD platforms: Travis CI, CircleCI, and GitHub Actions (in the cloud) provide fully managed CI/CD environments that eliminate the need to maintain in-house CI servers. These services integrate with code repositories on GitHub, Bitbucket, etc., and facilitate the configuration of test and deployment pipelines.

  • Code review tools: GitHub, GitLab, and Bitbucket not only host code, but also offer functionalities for code review, pull requests and discussions that are essential to the TDD process.
  • Automated testing platforms: Sauce Labs and BrowserStack allow UI testing to be run on browsers and mobile devices, facilitating the validation of web and mobile apps in real-world environments without the need for proprietary infrastructure.

 

Anti-patterns that hinder TDD

 

When developing software, it is possible to fall into common mistakes or bad practices that often hinder our processes and waste our time and energy. These wrong habits that we repeatedly fall into, usually without realising it, are what we call anti-patterns. If we don't learn to identify them, they are likely to keep us away from our goal: developing quality software.

 

For some time now, Matheus Marabesi, software craftsperson at Codurance, has been fascinated by TDD anti-patterns. This led him in 2021 to send out a survey, worldwide, to anyone interested in and/or using TDD in their workplace to collect their experiences.

 

The survey results helped shape a 6-part series in which Matheus reviews the list of 22 TDD anti-patterns compiled by James Carr. 

 

Through workshops, katas and blogs Matheus, together with several fellow craftspersons, offers keys to learn how to identify anti-patterns and avoid falling into the trap of certain tests that generate very large code bases and lead to slow feedback processes.

 

Subsequently, the survey and videos became the basis of their new and most recently updated (January 2024) eBook: Common Patterns that make TDD harder: An essay from practitioners. You can download it for free here and share it in your team to start applying TDD.

Cover Common patterns that make TDD harder
Download the TDD antipatterns eBook and find out what are the most common pitfalls and how to avoid them.

 

Download e-book


 

 

How to improve my TDD skills?

 

Improving your TDD skills is a long-term journey, in which constant practice is crucial. As with any discipline in which you seek to improve your level, the more you become familiar with using TDD in your daily practice, the more you will learn to overcome challenges and increase your mastery.

 

Increasing your sources of learning is also useful; for example, studying real use cases to analyse how other development teams apply TDD can provide you with new perspectives and techniques. Also, joining seminars and workshops is of utmost importance, as you will focus on learning from experts and get real-time feedback and advice on your practice.

 

At Codurance we encourage community learning, and a community of practice can be an interesting place to exchange ideas and experiences to sharpen your skills. In this section we offer you resources and references that could help you increase your TDD knowledge. Keep in mind that the pursuit of excellence is a lifelong journey.

 

EN_PillarTDD_1

 

 

How to assess my TDD level?

 

Determining your TDD level involves considering several aspects, from your understanding of the fundamental principles to your ability to apply these principles in practice.

 

Aspects evaluated include understanding the fundamental principles and benefits of TDD, the ability to write effective tests, how well TDD is integrated into workflows, and how new learning is acquired.

 

By recognising your strengths and areas for improvement, you can chart a clear path towards continuous improvement in your TDD practice.

 

 

Learning Paths

 

If you are looking for a learning path designed to build your TDD skills gradually, here are 3 plans depending on your level:

 

Beginner level plan, where you'll go from the foundations to more advanced concepts, and equip yourself with the tools you need to effectively incorporate TDD into your software development projects.

 

Intermediate level plan, to dive deeper into advanced principles, refine your technique and learn how to apply TDD in more complex contexts.

 

Advanced level plan, designed to challenge you and push your TDD boundaries.

 

 

Katas and Training

 

If you are new to TDD and want to start with a solid foundation to help you generate tests efficiently, these are the katas for you. Train your skills from the beginning.

 

If you are already an experienced TDD developer, but you want to keep increasing your knowledge or check if you are falling into some anti-patterns, here are some challenges: Object-Oriented Programming Katas and Refactoring Katas.

 

Already a TDD expert? Then you need katas that give you a real challenge and push you to keep improving your level. Here is a list of exercises that will test your skills.

 

 

Resources to broaden my knowledge

 

If you want to follow good practices that will help you increase your knowledge in Test Driven Development, here we share several resources.

 

Books and articles on TDD

 

There are many valuable resources available for those looking to delve deeper into this methodology. Below is a selection of recommended books and articles on TDD that offer everything from an introduction to advanced tips and case studies.

 

1. Test-Driven Development: By Example by Kent Beck. A must-have for every developer. Kent Beck, one of the pioneers of TDD, guides the reader through practical examples, showing how to develop high quality software iteratively.

 

2. The Software Craftsman: Professionalism, Pragmatism, Pride by Sandro Mancuso. Covers a wide range of topics related to technical excellence and tips for bringing professionalism, pragmatism and pride to our industry.

 

3. Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin. Addresses clean coding and agile development practices, including the importance of unit testing and TDD.

 

4. Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce. Focusing on object-oriented development, this book offers an in-depth perspective on how to build robust software systems using TDD.

 

5. Common Patterns that make TDD harder: An essay from practitioners by Matheus Marabesi and Emmanuel Valverde. A guide to creating code tests that boost quality and efficiency in development cycles.

 

6. The New Methodology by Martin Fowler. Explores agile methodologies and provides an overview of how these practices can transform software development.

 

7. Test-Driven Development in C# Corner. An introductory article on TDD covering the fundamentals, benefits and an example of how to implement TDD.

 

8. Should we always use TDD to design? by Sandro Mancuso. Discusses the use of TDD as a software design methodology.

 

9. Introduction to Test Driven Development in Agile Data. This article provides a clear overview of TDD principles.

 

10. Why Most Unit Testing is Waste by James O Coplien. Offers a critical perspective on TDD to reflect on how and why we apply this practice.

 

 

TDD Workshops and Events

 

Check out our upcoming events where we gather to learn as a community. Also, here is a selection of workshops and meetups on TDD where our craftspeople and guest practitioners teach us, through their experience, how to apply TDD in different contexts.

 

Outside-in TDD. Different from previous screencasts, this one is not meant for TDD beginners. In this 3-part screencast you will be able to follow Sandro Mancuso's "just in time" design approach. Thinking out loud throughout the video, it becomes quite easy to understand everything that he is considering while writing each test and refactoring his code. This video provides a very good example of Outside-In TDD and how it differs from the Classicist approach.

 

Testing in Python. Javier Martínez, Data Engineer, offers a hands-on session in which he presents different examples that will help us to understand the importance of testing in any phase of software development. The aim is to increase our agility and comprehension in code development through testing. 

 

Blind Outside TDD in React. Arnaud Claudel, Software Craftsman, offers a workshop on Outside-in TDD in React. This session contains two parts: A more theoretical introduction to analyse common problems when testing in React and a second part to get hands-on with two exercises, one on Front End logic and the other focused on UI and Design.

 

 

TDD thought leaders to follow 

 

In the world of Test Driven Development (TDD), there are several leaders whose contributions, teachings and practices have shaped the way we understand and apply TDD today. Among the biggest names are Kent Beck, TDD pioneer, and Martin Fowler, expert in software design, refactoring and agile development.

 

At Codurance we are fortunate that one of our founders, Sandro Mancuso, is a pioneer of Software Craftmanship and regularly publishes insights on industry trends and best practices. Next to him, it is impossible not to mention Robert C. Martin (Uncle Bob), author of several essential works on software engineering and advocate of TDD.

 

Rebecca Wirfs-Brock is also known for her work on object-oriented design and agile development. As well as James Shore, a software expert who offers resources for going deeper into TDD, especially in the field of JavaScript.

TDD Podcasts
Listen to our expert craftspeople discussing TDD and sharing lessons learned from their experiences.

Increase your team's potential

 

Our comprehensive set of training programs ensures that your teams have a high level of technical competence to improve business results.