What is test-driven development (TDD)?

Two software developers looking at a computer screen

Authors

Josh Schneider

Staff Writer

IBM Think

Ian Smalley

Staff Editor

IBM Think

What is test-driven development (TDD)?

Test-driven development (TDD) is an approach to software development in which software tests are written before their corresponding functions.

Developers write enough code to pass each test, then both the test and code are refined before moving onto a new test and then a new feature.

Test-driven development essentially forces developers to slow down, validate and refine their code in shorter feedback cycles. While not required, DevOps teams encourage coders, from beginners to seasoned professionals, to use TDD across a wide range of programming languages. For example, Java, Python, and so on, application programming interfaces (APIs) and program applications.

Programming in this style strengthens the relationship between coding, testing (in the form of automated unit-level tests) and code design. While test-driven development might increase upfront development time, it has been demonstrated to improve code functionality and dexterity and save time overall.

By immediately identifying and addressing any errors, developers that use TDD can prevent small issues from becoming larger problems. Test-driven development forces developers to both validate and refine their code as they go, thus streamlining final quality checks and corrections. 

Alternative testing frameworks include writing the production code before writing all the automated tests or writing the entire test suite before writing the production code. These methods, while not necessarily ineffective, have been shown to increase necessary debugging times, especially with larger and more complex projects.

While test-driven development is commonly used for the creation of new production code, it is also often applied to improve debugging legacy code developed with older or other techniques. 

Test-driven development reverses the traditional development process by putting testing before development. As an iterative approach, test-driven development improves code quality and readability by promoting testable workflows that result in high-quality code at the unit level. When developers implement unit testing, they focus on a small portion of logic, such as an individual algorithm. Writing code specifically to make tests pass not only results in cleaner, more durable code but also helps improve documentation. 

The latest tech news, backed by expert insights

Stay up to date on the most important—and intriguing—industry trends on AI, automation, data and beyond with the Think newsletter. See the IBM Privacy Statement.

Thank you! You are subscribed.

Your subscription will be delivered in English. You will find an unsubscribe link in every newsletter. You can manage your subscriptions or unsubscribe here. Refer to our IBM Privacy Statement for more information.

Levels of test-driven development

There are two main levels of test-driven development.

Acceptance TDD (ATDD)

For acceptance TDD—sometimes called Behavior-Driven Development (BDD)—programmers write a single acceptance test and then enough new code to pass. Acceptance tests are sometimes referred to as customer tests or customer acceptance tests.

They can generally be understood as test cases required for minimum functionality as outlined by product stakeholders. ATDD strives to identify detailed, executable requirements. Acceptance tests can be carried out using various testing tools, such as Fitnesse or RSpec.

Developer TDD

Sometimes referred to as simply TDD, developer TDD requires coders to write single tests to evaluate their own solution to an ATDD test. Developer TDD uses test automation tools, such as JUnit or VBUnit. 

AI Academy

The rise of generative AI for business

Learn about the historical rise of generative AI and what it means for business.

5 steps of the test-driven development cycle

When employing a test-driven development strategy, coders first write tests to check each individual element or function of a piece of software before writing enough code to pass that individual test. Once completed, the software is tested again, and if it passes, the code is refined (a process known as refactoring) to include only essential elements. Developers then repeat this process for each subsequent software function. 

The test-driven development process is broken down into five individual steps:

  1. Before writing the code for a certain software function, developers first write an individual unit test for that function.
  2. Developers then run the test, which should fail because the code function hasn’t been written yet. This step is important to confirm that the test itself is functional and does not return any false positives. If the code passes, it indicates that the test needs to be rewritten.
  3. When the program fails the test, developers write only enough extra software code to pass the test. 
  4. When the code can pass the test, both the test and the code are refactored for simplicity and to eliminate any unnecessary code. 
  5. When the sufficiently refactored software can pass the refactored test, developers move on to the next wanted software function. Testers then write and run tests for each new feature. 

Put simply, the test-driven development process follows a repeatable loop, referred to as the red-green-refactor cycle. The steps of the cycle are:

  • Red: Write a failing test for the intended software behavior. 
  • Green: Write enough extra to pass the test.
  • Refactor: Refine the code to meet simplicity standards as much as possible while still passing the test.

History of test-driven development

While the specific origins of test-driven development are unknown, the concept of writing tests first and production code second was not a common practice until the mid-1990s. Before that, testing frameworks separated developers from testing their own codebases. However, as software engineering evolved, DevOps teams demanded faster and more flexible methodologies to meet stakeholder demands, especially when dealing with rapidly shifting stakeholder requirements. 

Test-driven development evolved from and alongside various novel testing frameworks and has been adopted as a modular component into various other frameworks, as well. Most notably, TDD is included in the concept of Extreme Programming (XP), an agile software development framework developed to improve both software quality and the quality of life for developers.

Software engineer Kent Beck, a major figure in the agile community and the creator of Extreme Programming, is credited with “rediscovering” test-driven development. In Beck’s own words

“The original description of TDD was in an ancient book about programming. It said that you take the input tape, manually type in the output tape you expect, then program until the actual output tape matches the expected output. After I’d written the first xUnit framework in Smalltalk, I remembered reading this and tried it out. That was the origin of TDD for me. When describing TDD to older programmers, I often hear, “Of course. How else could you program?” Therefore, I refer to my role as “rediscovering” TDD.” 

Notable dates in the evolution of test-driven development include:

  • 1976: Glenford Myers publishes Software Reliability, in which he argues that “a developer should never test their own code.” While Myers might not have been the originator of this concept, his work gave credence to a common notion that would pervade for years to come. 
  • 1990: At the beginning of the decade, the “black-box” technique dominated software testing. In this kind of testing framework, testers treat the software as though it were a “black box,” impenetrable and unknowable. Black-box testing employs testers who, critically, have no knowledge of the software’s inner workings. 
  • 1994: Kent Beck develops SUnit, a Smalltalk testing framework, laying out the groundwork for a test-first approach for codebase optimization. 
  • 1999–2002: As the agile development movement gathers steam, Kent Beck develops the concept of Extreme Programming, codifying test-driven development and introducing the critical concept of mock objects. TDD uses mock objects to simulate the behaviors of real dependencies (for example, databases, external services, and so on) during testing. This method helps developers focus their test code on maintainable mock objects that can be verified to perform accurately. A failing test that uses a mock object can eliminate a potentially misconfigured dependency as the source of the failure. 
  • 2003: Kent Beck publishes Test Driven Development: By Example, popularizing the practice among the broader development community and further legitimizing developer-driven testing. 

Benefits of test-driven development

As a component of Extreme Programming, test-driven development has been found to be beneficial for not only creating better code, but also better coders themselves. TDD can allow coders to gain better insight into their projects and help drive program design. By centering the test case before each feature is implemented, developers must visualize how a function will be used by a client or user. This approach positions the product interface before the implementation and helps developers create more user-centric applications. 

Some additional benefits of test-driven development include:

  • Comprehensive test coverage: TDD is sometimes referred to as a specification or documentation tool because the practice ensures that all code is covered by at least one test. 
  • Improved documentation: For the same reason that TDD provides comprehensive coverage, it also provides robust documentation and specification. This system helps developers, project managers and other stakeholders validate code features and requirements and establish order through the project lifecycle. 
  • Improved confidence: Developers and DevOps teams that use TDD gain greater confidence in not only their code but also their testing. 
  • Facilitates continuous integration: TDD is well-suited for continuous integration practices, in which live code is continuously updated with new features and patches. 
  • Reduced debugging: TDD front-loads testing in the development process, reducing the need for extensive debugging at the tail end of development. 
  • Improved requirement clarity: TDD helps developers establish a clear understanding of each specific program requirement before beginning work. 
  • Improved productivity: TDD is often linked with increased productivity among developers as the process helps atomize large projects into smaller, more achievable steps. 
  • Reinforces simple design: The critical third step in the green-red-refactor TDD cycle requires developers to refactor and simplify their code. This practice improves general simplicity and quality design. 
  • Strengthens mental models: By examining and integrating every unique function or requirement, TDD helps coders develop a strong mental model of the code at hand. This mental model helps developers visualize the overall functions and requirements of the code as they are working on it. 
  • Improved system stability: The use of test-driven development has been demonstrated to improve overall application stability by creating robust, well-tested code aligned with high standards for simplicity in design. 

Test-driven development challenges 

Although there are many valuable benefits to using test-driven development (TDD), it is not without its challenges. While the severity of these challenges can be project-dependent or mitigated with various other techniques, some of the disadvantages of TDD include:

  • Increased code volume: TDD requires coders to not only write code for each required feature, but also tests for each feature. Adding the test code with the product code results in a larger overall codebase. 
  • False confidence: As each feature is written to pass a test, coders and project managers can develop a false sense of security in the overall code's functionality. Even though each integrated feature is tested, TDD does not replace the need for final quality control and API testing
  • Increased overhead: TDD requires coders to also maintain a large suite of tests in addition to their production code. Maintaining test codebases requires a certain amount of resources and can add to overhead costs. 
  • Decreased efficiency: While TDD has been shown to improve productivity, it can delay project development as it adds more steps to the creation and implementation of each new feature. 
  • Increased set up time: TDD requires developers to set up and maintain suitable testing environments for their code. 
  • Neglect of overall design: Although TDD encourages code simplicity and improved design, focusing too much on individual components can lead to a less harmonious overall code. Coders that use TDD need to know how their individual feature updates will integrate when compiled into the overarching software application. 
Related solutions
IBM Hashicorp

Helps simplify complex hybrid environments with unified infrastructure and security management.

Explore IBM HashiCorp
DevOps solutions

Build, deploy and manage secure, cloud-native apps across devices and environments.

Explore DevOps solutions
Cloud Platform Engineering services

Set a new standard for cloud and hybrid operations.

Explore cloud platform engineering services
Take the next step

Discover how HashiCorp and DevOps solutions simplify hybrid infrastructure, unify lifecycle management, and accelerate secure, cloud-native app delivery.

Discover IBM HashiCorp Explore DevOps solutions