Explore six effective strategies for resolving failing tests. Four of these methodologies derive from Kent Beck's influential work, "Test Driven Development: By Example," and are further enriched with my personal insights. The remaining two strategies are unique contributions developed from my own experiences.

A helpful guiding principle to adopt when TDDing is the 'two attempts rule.' Make two concerted efforts to pass the test. If unsuccessful, employ the 'back out' pattern to revert any changes, and then approach the problem anew. This practice fosters a structured problem-solving approach that can improve the efficiency and effectiveness of your test-driven development.

Fake It

This pattern is the simplest and the most utilized when building context. It is useful if you do not have examples or the examples are unclear. If you have 2 or more good examples use triangulation instead. The key to applying this pattern is to work in small increments until you find a generalized solution. To achieve this start off with returning a constant, then gradually transform that into an expression with variables. Your brain is an amazing pattern matching machine, this green bar pattern makes use of this fact.

The pattern is often applied in the following way:

  1. Start with a failing test
  2. Make the test pass as quickly as possible. This often means returning a constant value.
    For example:
    if(value == 3) return “Fizz”;
  3. Refactor only once all your tests are green.

Triangulation

This pattern involves working with known examples and is great for building context. It feels very similar to the Fake It pattern in that when it starts off with hard-coding the specific examples in an attempt to find the general solution later.

After 2 or more specific examples, one should see a pattern emerging. It can take more than 3, if after 6 you still cannot see the pattern hit reverse gear, you are stuck and likely making a mess.

The goal of this pattern is to have used examples to guide the implementation to find a generic one that solves the solution allowing you to remove the hardcoded details.

As you move through the examples, you must vary your expected. E.g. when doing the age calculator, you should expect an age of 1, 2, and 3 respectively for the first three test. Failure to vary the expected result from one test to the next means you are not making forward progress of the implementation.

Be mindful of the need to refactor your test code as well as your production code when completing a Triangulation; this is because test code is more valuable than production and should be treated as such. When triangulating, the most common test refactoring is to roll up related test into a single test case.

Also, be mindful of what portion of the problem you are solving, trying to use this technique to solve many different portions of the problem at once can lead to a big ball of mud - stay focused on solving the problem one variable at a time.

One-to-Many

This pattern is useful for moving from a single item to a collection of items. This is because it is easier to write an implementation that works with a single value rather than a collection. By focusing on solving for a single value, one can focus on the core part of the problem being solved and only worry about the collection detail once the foundations have been established.

Suppose we were creating an undo stack to track operations and allow them to be undone. We would start by only supporting a single operation to get started. Once we were comfortable that we had the concept working we can shift focus to handling many operations, thus expanding our focus from the core of the problem to the detail of the problem.

There are two common variants of this pattern. The first involves refactoring one test while the second involves using two tests. It is likely you will make use of one or both variations when applying this pattern.

Variation 1 – Evolving a public method

In this variation, I first get a test to pass dealing with a single instance of an object through a public method. I then refactor the implementation to make use of a collection, thus causing a compilation failure and a need to refactor my test to pass through a collection with 1 item.

An example of this is the first test of the CSV kata. I write a test to pass through a single customer to the Write method. This allows me to write up the implementation without worrying about the details of dealing with a collection.

  [Test]
  public void Write_WhenSingleRecord_ShouldWriteToFile()
  {
      //---------------Arrange-------------------
      var fileName = "import.csv";
      var expectedLine = "Bob Jones,0845009876";
      var fileSystem = Substitute.For();
      var writer = new CsvFileWriter(fileSystem);
      var customer = new Customer { Name = "Bob Jones", ContactNumber = "0845009876" };
      //---------------Act---------------
      writer.Write(fileName, customer);
      //---------------Assert ----------------------
      fileSystem.Received(1).WriteLine(fileName, expectedLine);
  }

  public void Write(string fileName, Customer customer)
  {
      if (customer == null) return;
      if (string.IsNullOrWhiteSpace(fileName)) return;

      _fileSystem.WriteLine(fileName, customer.ToString());      
  }
  

Having solved the core problem of writing a single customer to a file I can now focus on amending the test and implementation to handle a collection. By changing the public method’s signature I can use the compilation failure to drive the need to adjust my test. This is because compilation failures are first class test failures.

  public void Write(string fileName, List customers)
  {
      if (customers == null) return;
      if (string.IsNullOrWhiteSpace(fileName)) return;
  
      foreach (var customer in customers)
      {
          _fileSystem.WriteLine(fileName, customer.ToString());
      }
  }

I now need to amend my Test to pass through a list with a single customer. I still have the same expectation in the test, I just need to restructure the test to fix the compilation failure. By using the compiler I can safely make structural changes to my test without violating any of the TDD Cycle or characteristics of a good tests.

  [Test]
  public void WhenSingleRecord_ShouldWriteToFile()
  {
      //---------------Arrange-------------------
      var fileName = "import.csv";
      var expectedLine = "Bob Jones,0845009876";
      var fileSystem = Substitute.For();
      var writer = new CsvFileWriter(fileSystem);
      var customer = new List {new Customer{ Name = "Bob Jones", ContactNumber = "0845009876" }};
      //---------------Act----------------
      writer.Write(fileName, customer);
      //---------------Assert ----------------------
      fileSystem.Received(1).WriteLine(fileName, expectedLine);
  }
  

Variation 2 – Isolating private method complexity

In this variation I first get my solution to work with a single instance of something internally, usually a private method. This allows me to solve the core problem without worrying about all the implementation details. Once I have solved the core problem, I then create a second test to address the need to deal with a collection of items in my private method.

An example of this is the negative numbers tests when doing the String Kata. I first create a test to check for 1 negative and then I write the implementation needed to handle a single negative. This allows me to strip away some of the complexity needed to handle collections, thus enabling me to focus better on the core of the problem.

  [Test]
  public void Add_WhenNegativeNumber_ShouldThrowExceptionWithNegative()
  {
      //---------------Arrange-------------------
      var input = "-1";
      var calculator = new StringCalculator();
      //---------------Act----------------------
      var result = Assert.Throws(() => calculator.Add(input));
      //---------------Assert-----------------------
      var expected = "negatives not allowed: -1";
      Assert.AreEqual(expected, result.Message);
  }

  private void ThrowExceptionIfNegatives(IEnumerable integers)
  {
      var number = integers[0];
      if (number < 0)
      {
          throw new Exception($"negatives not allowed: {number}");
      }
  }
  

Once I have solved the core problem, I then move on to creating a new test to handle a collection of negatives when formatting the exception’s message. By waiting to focus on the collection details I am able to address this portion of the requirements after solving the core problem, detecting negative values and throwing an exception.

  [Test]
  public void Add_WhenNegativeNumbers_ShouldThrowExceptionWithAllNegatives()
  {
      //---------------Arrange-------------------
      var input = "10,5,-1,6,-3,-9";
      var calculator = new StringCalculator();
      //---------------Act----------------------
      var result = Assert.Throws(() => calculator.Add(input));
      //---------------Assert-----------------------
      var expected = "negatives not allowed: -1,-3,-9";
      Assert.AreEqual(expected, result.Message);
  }
  

Once the new test is failing, I then move into amending the implementation to being more robust.

  private void ThrowExceptionIfNegatives(IEnumerable integers)
  {
      var negatives = integers.Where(x => x < 0);
      if (negatives.Any())
      {
          var negativesString = string.Join(",", negatives);
          throw new Exception($"negatives not allowed: {negativesString}");
      }
  }    
  

Obvious

This is the most tempting pattern to use when applying TDD. Its application is simple, if the implementation is obvious implement it.

The trick to properly using this pattern is to realize that the obvious implementation is not so obvious. One can become stuck quickly and frequently when solving even simple problems. If you find yourself using this a lot it is likely you are overestimating your thinking capabilities. It is likely you will fail to take small steps and end up with a big ball of mud if you even manage to solve the problem.

A better way to think of this pattern is this, only follow this pattern if the implementation is trivial. If you coded an obvious implementation and you are struggling to get your test to pass, it is time to switch to reverse gear and try a new pattern.

Back Out

This is a special green bar pattern that I developed and is only applied in cases when you are stuck trying to get the current test to pass.

The process is as follows:

Learning Test

A learning test is a special type of test used to isolate and validate a snippet of code when applying the Back Out pattern. A learning test is used to bring focus to a problem area isolating it and bringing it under test. The technique can be applied to the code at any time, not just when applying the Back Out pattern. There are three well-known cases when you would want to use a Learning Test.

Case 1 – Struggling to refactor to a generalized solution

The first is when you are struggling to complete a refactor like generalizing after a Triangulation. The example below illustrates this exact case.

Test Example

The example below is from the Fizz Buzz Whiz kata’s bonus section. An additional requirement to return/append Whiz when the number is prime has been added.

In the example, I used triangulation to arrive at the current test with three test cases. I now want to convert to a generic implementation, but I am unsure how to.

  [TestCase(2)]
  [TestCase(7)]
  [TestCase(97)]
  public void GetResult_WhenNumberPrimeNot3or5_ShouldReturnWhiz(int input)
  {
      //---------------Set up test pack-------------------
      var fizzBuzzer = new FizzBuzzer();
      //---------------Execute Test ----------------------
      var result = fizzBuzzer.GetResult(input);
      //---------------Test Result -----------------------
      var expected = "Whiz"
      Assert.AreEqual(expected, result);
  }
  

I have the following production code at the end of the Triangulation process.

  private static bool IsPrimeNumber(int input)
  {
      if (input == 2 || input == 7 || input == 97)
      {
          return true;
      }

      return false;
  }    
  

To help bridge the gap I create a learning test to isolate and explore prime number calculation. I use the naming convention LearningTest_Goal_{GoalOfTest} to help add clarity to my endeavors.

  [Test]
  public void LearningTest_Goal_CalculatePrimes()
  {
      // arrange
      var canidateNumber = 2;
      var expected = true;
      // act
      var isPrime = Enumerable
          .Range(1, canidateNumber).Where(x => canidateNumber % x == 0)
          .SequenceEqual(new[] { 1, canidateNumber });

      // assert
      Assert.AreEqual(expected, isPrime);
  }
  

Once I complete my learning I roll it back into the production code by replacing the inner workings of the IsPrimeNumber method.

Case 2 – Struggling to get a green test

The second is when you are struggling to get a test green. This is often a sign of hidden assumptions about the problem domain or some misunderstood technical issue. When you find yourself stuck, isolate the issue with a learning test.

Case 3 – Exploring

There are often times when dealing with a new interface of technology you want to understand it before you try and use it. Using a learning test to isolate your work and clarify your goal is a great way to quickly understand the unknown.

The technique works the same for all cases it is just the circumstances driving the need to create the tests that differs.