Test Driven Development

Test Driven Development (TDD) is a software development approach in which developers write tests before they write code for new features. The code is changed to make the tests pass and subsequently, is refactored to meet the functional and non-functional requirements. This sequence of steps is repeated until all test cases for all features pass and can be marked as successfully completed. In TDD three activities are tightly interwoven: coding, testing (in the form of writing unit tests), and design (in the form of refactoring).

By validating them against a series of agreed-to tests, TDD— an Agile Testing practice—improves system outcomes by assuring that the system implementation meets its requirements. TDD, along with Behavior-Driven Development (BDD), is part of the test-first approach to satisfy Built-in Quality. Generally, XP follows this practice. These unit tests are run in an automated fashion during the build and integration stage. 

Primary Phases of Test Driven Development

  • Create precise tests: Developers need to create precise unit tests to verify the functionality of specific features. They must ensure that the test compiles so that it can execute. In most cases, the test is bound to fail. This is a meaningful failure as developers are creating compact tests based on their assumptions of how the feature will behave.
  • Correcting the Code: Once a test fails, developers need to make the minimal changes required to correct the code so that it can run successfully when re-executed.
  • Refactor the Code: Once the test runs successfully, check for redundancy or any possible code optimizations to enhance overall performance. Ensure that refactoring does not affect the external behavior of the program.

Steps of Test Driven Development

  • The first step is to understand the requirements, features, or user story and write a corresponding test for it. The test case could be like a Junit or Nunit depending on the programming language used. 
  • The second step is to run the test case, which will obviously fail because there is no valid code to execute. This is the red step.10 
  • In the next step, stubs of code are written that successfully compile and can be invoked by the test case. At this stage if the test is run, it should call the code stub but still fail because it lacks functionality. 
  • In this stage, sufficient code is now written that makes the test case pass. One can hard-code return values, use constants, mocked-up objects, and any other means to satisfy the test condition. This is the beginning of the green step. 
  • During the next stage, the developer keeps adding code to address the functional and non-functional requirements, making sure that the linked tests continue to pass. If there are more features to be added, new test cases have to be written first and the process repeats itself. 
  • In the refactor or clean stage, the code is updated to address any code quality issues, simplify of design, eliminate technical debt, and remove any hard- coding, duplication, unused parameters, methods, classes, unnecessary log statements, etc. Note that while these technical changes are made, the behavior of the code is kept intact and the tests should continue to pass. 
  • The above cycle is repeated for each story, feature, and functionality throughout all iterations.

Step-by-Step Example: Adding Two Numbers

Step 1: Write a Test

First, you write a test for the function you want to create. In this case, we want to create a function add(a, b) that returns the sum of a and b.

class TestAddFunction(unittest.TestCase):
def test_add_two_numbers(self):
self.assertEqual(add(2, 3), 5)

if __name__ == '__main__':
unittest.main()

Step 2: Run the Test and See It Fail

Run the test to make sure it fails. This step confirms that the test is correctly identifying that the function does not yet exist.

python test_add.py

Output:

E
======================================================================
ERROR: test_add_two_numbers (__main__.TestAddFunction)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_add.py", line 5, in test_add_two_numbers
self.assertEqual(add(2, 3), 5)
NameError: name 'add' is not defined

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Step 3: Write the Minimum Code to Pass the Test

Write the simplest code to make the test pass. Create the add function that returns the sum of a and b.

def add(a, b):
return a + b

Step 4: Run the Test and See It Pass

Run the test again to ensure it passes with the new code.

python test_add.py

Output:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Step 5: Refactor the Code

Refactor the code if necessary to improve its structure without changing its behavior. In this simple example, there might not be much to refactor.

Step 6: Repeat

Repeat the cycle by writing more tests and expanding the functionality. For example, you might add more tests to handle edge cases or different scenarios.

class TestAddFunction(unittest.TestCase):
def test_add_two_numbers(self):
self.assertEqual(add(2, 3), 5)

def test_add_negative_numbers(self):
self.assertEqual(add(-1, -1), -2)

def test_add_zero(self):
self.assertEqual(add(0, 0), 0)

def test_add_positive_and_negative(self):
self.assertEqual(add(10, -5), 5)

if __name__ == '__main__':
unittest.main()

Run the tests again to ensure all of them pass.

python test_add.py

Output:

....
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

Summary

  1. Write a test: Define what the function should do.
  2. Run the test and see it fail: Confirm that the test correctly identifies the absence of the functionality.
  3. Write the minimum code to pass the test: Implement the simplest solution to make the test pass.
  4. Run the test and see it pass: Ensure the new code meets the test requirements.
  5. Refactor the code: Improve the code without changing its behavior.
  6. Repeat: Add more tests and expand functionality incrementally.

Advantages of TDD

  • The programmer is forced to write only that amount of code that passes the test, nothing more than that, since it is wasteful. This follows the principles of “Do not Repeat yourself” (DRY).
  • Teams practicing TDD often write just enough code and tend to not over-engineer. Tests are a client of the system which results in less speculation on what code may be needed by other clients. This focus leads to less code to implement, test, and maintain. If followed religiously, TDD could result in better code coverage and code quality. 
  • Since the success criteria are defined upfront, developers concentrate only on getting the test case to pass. This keeps the code simple and free from unnecessary clutter. 
  • A team following TDD naturally makes sure that user stories, when they are created, are testable (T in the INVEST acronym). 
  • The unit test cases are written in the same programming language and stored on the same version control repository where code is checked in – so the developers are not only familiar with it but also own it. There is no need to maintain separate test case documentation elsewhere. 
  • With refactoring, there is a continuous focus to contain and remove technical debt, thereby making the code easier to maintain, make changes, and understandable by the team. 
  • The suite of tests can be run over and over again (many times a day) to prove that the software continues to work irrespective of adding newer functionalities. 
  • The use of TDD shortens the feedback cycle, as once the code is written, built, and executed, the test results are immediately ready in a matter of minutes. This supports the concept of frequent validation and verification. 
  • The successful outcome of TDD contributes to the ‘definition of done’. With a test-first focus, scrum teams build stronger collaboration and leave less room for misunderstanding. Developers within the team work together on what it means for a product backlog item to be correctly implemented and considered done. This happens before any code is created and is confirmed as the code is written. This practice is typically done with both a software developer and quality assurance engineer working together rather than handing off code from one to another.
  • The practice is also shown to boost customer and stakeholder confidence in the product by knowing that the scrum team has a strong commitment to delivering a high-quality product.
  • One extra benefit of TDD is with each product backlog item the team builds up a test automation suite that can be run to give quick feedback on the state of the system. Tests can be run as part of your continuous integration build and by developers on their local machines as they make changes. When all the tests pass your team is confident the system is behaving as intended.

Disadvantages of TDD

  • As with any new practice, there’s an initial learning curve with TDD. The team and its stakeholders need to understand that they’re going to have to initially slow down in order to speed up over time.
  • As the same developer who writes the tests also writes the code to pass it, it is pretty much left to the imagination and level of interpretation of that developer. 
  • Not all code like that behind user interfaces can be reliably and efficiently unit-tested. Teams will have to complement TDD with other forms of testing like exploratory testing to achieve the end purpose. 
  • Maintaining a test suite over the life cycle of the project is essential and should be a key consideration that the team will have to factor in during their planning and estimation exercises. 
  • Forgetting to run tests frequently, writing too many tests at once, writing tests that are too large or coarse-grained, and writing overly trivial tests, for instance, omitting assertion and writing tests for trivial code, for instance, accessors are a few disadvantages of TDD.

Table of Contents (PSM ™, PSPO ™ & PSD ™ Certification)

  1. Scrum Framework – Scrum Theory
  2. Scrum Framework – Scrum Roles
  3. Scrum Framework – Scrum Events
  4. Scrum Framework – Scrum Artifacts
  5. Scaling Scrum – DoD – DoR – Scrum Framework
  6. Developing People and Teams
  7. Managing Products with Agility
  8. Developing and Delivering Products Professionally
  9. Evolving the Agile Organization – Scaled Scrum, Portfolio Planning & EBM

Recommended Practice Exams

Owner, PSPO, PSPO I, Scrum Open, etc. are the protected brand of Scrum.org. All the content related to Scrum Guide is taken from scrumguides.org and is under the Attribution ShareAlike license of Creative Commons. Further information is accessible at https://creativecommons.org/licenses/by-sa/4.0/legalcode and also described in summary form at http://creativecommons.org/licenses/by-sa/4.0/.

Scroll to Top