Feeds:
Posts
Comments

Archive for the ‘TDD’ Category

Consider the goal:

Get the total value of some numbers

and the following test requirement

given two numbers their sum should be returned

with implementation details

the input is a two-item array containing the values 5 and 0 in that order

The passive aggressive TDD practitioner might take advantage of his knowledge of the flaw in the test implementation details (the second array item doesn’t affect the sum) to implement his interpretation of “the simplest thing that could possibly work”.

return the first array item

or even less usefully

return 5

Either way the test passes but the spirit of the requirement has not been met. Unarguably the spirit of “the simplest thing that could possibly work” has not been met either. The test writer must now use his knowledge of the implementation details to write a second test that forces the implementor to refactor toward the spirit of the desired functionality:

given two non-zero numbers their sum should be returned

and implementation details

the input is a two-item array containing the values 1 and 2 in that order

The implementor can’t use his knowledge of the test implementation details in an obstructionist manner this time but he can still provide a literal implementation for “the simplest thing that could possibly work”.

return the sum of the first array item and the second array item

This game of “make me do the right thing” could go on and on with the test writer building ever more elaborate contracts and the implementor trying to find ways to wiggle out of them. No doubt if both developers are creative and tenacious they will evolve toward an implementation that achieves the goal and is extremely well wrapped in tests. However, this adds needless complexity and continuous technical debt that is just refactored out again in reaction to the next test, neither of which are the goal of doing “the simplest thing that could possibly work.”

Alternatively, the two developers could stop struggling against one another and work together on “the simplest goal-oriented thing that could possibly work.” By partnering on the test contract they could get a better, more succinct test the first time:

given two numbers their sum should be returned

with implementation details

the input is a two-item array containing only non-zero values

Next, ignoring their knowledge of the specific values being passed they can both meet the spirit of the test and provide a more durable solution toward the overall goal.

return the sum of the values in the array

Then they can ignore the implementation details and proceed to the next tests.

One way to practice this form of TDD is using the black box method. One person writes the test and the other writes the implementation. By agreement they do not look at the details of one-another’s code. This forces the test writer to write better, more descriptive tests and diagnostics that describe the desired behavior. It also forces the implementation writer to adhere to the spirit of the test instead of coding to its implementation. They will learn to focus on the What of the test rather than the How while keeping the feature goal in mind. Once the system is deemed complete the two can review one-another’s code to find gaps and flaws then swap roles and try a different kata.

Read Full Post »

BDD Kata – string calculator

My friend @scichelli tweeted about having done Roy Osherove’s String Calculator kata… again. I’d never heard of it so I hunted it down to try it out. Here goes!

Create a simple String calculator with a method int Add(string numbers) that takes 0, 1 or 2 numbers (for example “” or “1” or “1,2”) and returns their sum. For an empty string it will return 0.

[TestFixture]
public class When_asked_to_Add
{
	[Test]
	public void Given_an_empty_input()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(an_empty_input)
			.Should(get_zero)
			.Verify();
	}
}

test details

	[SetUp]
	public void BeforeEachTest()
	{
		_calculator = new StringCalculator();
	}

	private void an_empty_input()
	{
		_numbers = "";
	}

	private void asked_to_add()
	{
		_result = _calculator.Add(_numbers);
	}

	private void get_zero()
	{
		_result.ShouldBeEqualTo(0);
	}

which implies

public class StringCalculator
{
	public int Add(string numbers)
	{
		return -1;
	}
}

red

STEP 1: an empty input
WHEN asked to add
SHOULD get zero - FAILED

fix it

	public int Add(string numbers)
	{
		return 0;
	}

green. Next:

	[Test]
	public void Given_an_input_containing_a_single_number()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(an_input_containing_a_single_number)
			.Should(get_the_numeric_value_of_the_input)
			.Verify();
	}

	private void an_input_containing_a_single_number()
	{
		_numbers = "341";
		_expected = 341;
	}

	private void get_the_numeric_value_of_the_input()
	{
		_result.ShouldBeEqualTo(_expected);
	}

red

STEP 1: an input containing a single number
WHEN asked to add
SHOULD get the numeric value of the input - FAILED

fix it

	public int Add(string numbers)
	{
		if (numbers.Length == 0)
		{
			return 0;
		}
		return Convert.ToInt32(numbers);
	}

green. Now support two numbers

	[Test]
	public void Given_an_input_containing_two_numbers()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(an_input_containing_two_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	private void an_input_containing_two_numbers()
	{
		_numbers = "341,659";
		_expected = 1000;
	}

	private void get_the_sum_of_the_numeric_values_in_the_input()
	{
		_result.ShouldBeEqualTo(_expected);
	}

red

STEP 1: an input containing two numbers
WHEN asked to add
SHOULD get the sum of the numeric values in the input - FAILED

fix it

	public int Add(string numbers)
	{
		if (numbers.Length == 0)
		{
			return 0;
		}
		var values = numbers.Split(',').Select(x => Convert.ToInt32(x));
		return values.Sum();
	}

green. Next requirement:

Allow the Add method to handle an unknown amount of numbers

	[Test]
	public void Given_an_input_containing_more_than_two_numbers()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(an_input_containing_more_than_two_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	[SetUp]
	public void BeforeEachTest()
	{
		_expected = 0;
		_numbers = "";
		_calculator = new StringCalculator();
	}

	private void an_input_containing_more_than_two_numbers()
	{
		var random = new Random();
		for (int i = 0; i < 100; i++)
		{
			int randomValue = random.Next(5000);
			_expected += randomValue;
			_numbers += (_numbers.Length > 0 ? "," : "") + randomValue;
		}
	}

	private void get_the_sum_of_the_numeric_values_in_the_input()
	{
		_result.ShouldBeEqualTo(_expected, "input = "+_numbers);
	}

green. Next requirement:

Allow the Add method to handle either a newline or a comma between any two numbers. For example “1\n2,3” is OK but “1\n,3” is not.

First some test refactoring:

	[Test]
	public void Given_an_input_containing_two_numbers_separated_by_a_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(comma_separator)
			.With(an_input_containing_two_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_more_than_two_numbers_separated_by_a_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(comma_separator)
			.With(an_input_containing_more_than_two_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	private void comma_separator()
	{
		_separator = ",";
	}

Use _separator instead of hard-coded comma in the test details.

	private void an_input_containing_two_numbers()
	{
		_numbers = "341" + _separator + "659";
		_expected = 1000;
	}

	private void an_input_containing_more_than_two_numbers()
	{
		var random = new Random();
		for (int i = 0; i < 100; i++)
		{
			int randomValue = random.Next(5000);
			_expected += randomValue;
			_numbers += (_numbers.Length > 0 ? _separator : "") + randomValue;
		}
	}

still green. Now require support for newline separator too.

	[Test]
	public void Given_an_input_containing_two_numbers_separated_by_a_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(newline_separator)
			.With(an_input_containing_two_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	private void newline_separator()
	{
		_separator = "\n";
	}

red

STEP 1: newline separator
STEP 2: an input containing two numbers
WHEN asked to add - FAILED

fix it

	public int Add(string numbers)
	{
		if (numbers.Length == 0)
		{
			return 0;
		}
		var values = numbers
			.Split(',', '\n')
			.Select(x => Convert.ToInt32(x));
		return values.Sum();
	}

green. refactor.

	public int Add(string numbers)
	{
		if (numbers.Length == 0)
		{
			return 0;
		}
		var values = GetValues(numbers);
		return values.Sum();
	}

	private static IEnumerable<int> GetValues(string numbers)
	{
		return numbers
			.Split(',', '\n')
			.Select(x => Convert.ToInt32(x));
	}

Next add a test for multiple numbers with newline separator.

	[Test]
	public void Given_an_input_containing_more_than_two_numbers_separated_by_a_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(newline_separator)
			.With(an_input_containing_more_than_two_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

green. Next to handle the invalid case in the requirement, multiple separators together. Start with handling two commas.

	[Test]
	public void Given_an_input_containing_numbers_separated_by_more_than_one_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(comma_separator)
			.With(comma_separator)
			.With(an_input_containing_two_numbers)
			.ShouldThrowException<ArgumentException>()
			.Verify();
	}

	private void comma_separator()
	{
		_separator += ",";
	}

	[SetUp]
	public void BeforeEachTest()
	{
		_separator = "";
		_expected = 0;
		_numbers = "";
		_calculator = new StringCalculator();
	}

red.

STEP 1: comma separator
STEP 2: comma separator
STEP 3: an input containing two numbers
WHEN asked to add - FAILED

  Expected: <System.ArgumentException>
  But was:  <System.FormatException>

fix it

	private static IEnumerable<int> GetValues(string numbers)
	{
		var values = numbers.Split(',', '\n');

		if (values.Any(x => x.Length == 0))
		{
			throw new ArgumentException("Found multiple separators between values in '"+numbers+"'");
		}

		return values.Select(x => Convert.ToInt32(x));
	}

green. refactor.

	private static IEnumerable<int> GetValues(string numbers)
	{
		var values = numbers.Split(',', '\n');

		if (values.Any(x => x.Length == 0))
		{
			throw new MultipleSeparatorsException(numbers);
		}

		return values.Select(x => Convert.ToInt32(x));
	}

public class MultipleSeparatorsException : ArgumentException
{
	public const string FoundMultipleSeparatorsBetweenValuesFormat 
		= "Found multiple separators between values in '{0}'";

	public MultipleSeparatorsException(string numbers) :
		base(String.Format(FoundMultipleSeparatorsBetweenValuesFormat, numbers))
	{
	}
}

	public void Given_an_input_containing_numbers_separated_by_more_than_one_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(comma_separator)
			.With(comma_separator)
			.With(an_input_containing_two_numbers)
			.ShouldThrowException<MultipleSeparatorsException>()
			.Verify();
	}

still green. Try multiple newlines:

	[Test]
	public void Given_an_input_containing_numbers_separated_by_more_than_one_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(newline_separator)
			.With(newline_separator)
			.With(an_input_containing_two_numbers)
			.ShouldThrowException<MultipleSeparatorsException>()
			.Verify();
	}

	private void newline_separator()
	{
		_separator += "\n";
	}

green. Try alternating newlines and commas:

	[Test]
	public void Given_an_input_containing_numbers_separated_by_comma_and_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(comma_separator)
			.With(newline_separator)
			.With(an_input_containing_two_numbers)
			.ShouldThrowException<MultipleSeparatorsException>()
			.Verify();
	}

green. Next requirement:

Allow the Add method to optionally handle a different delimiter. To add a delimiter, the beginning of the string will contain a separate line that looks like this: “//[delimiter]\n[numbers…]” for example “//;\n1;2” should return 3.

	[Test]
	public void Given_an_input_containing_two_numbers_separated_by_a_space_with_space_delimiter()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(space_delimiter)
			.With(space_separator)
			.With(an_input_containing_two_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	private void space_delimiter()
	{
		UseDelimiter(' ');
	}

	private void UseDelimiter(char delimiter)
	{
		_delimiter = "//" + delimiter + "\n";
	}

	private void space_separator()
	{
		_separator += " ";
	}

	[SetUp]
	public void BeforeEachTest()
	{
		_delimiter = "";
		_separator = "";
		_expected = 0;
		_numbers = "";
		_calculator = new StringCalculator();
	}

red

STEP 1: space delimiter
STEP 2: space separator
STEP 3: an input containing two numbers
WHEN asked to add - FAILED

System.FormatException : Input string was not in a correct format.

fix it

	private static IEnumerable<int> GetValues(string numbers)
	{
		var delimiters = new List<char> { ',', '\n' };
		if (numbers.StartsWith("//"))
		{
			delimiters.Add(numbers[2]);
			numbers = numbers.Substring(4);
		}

		var values = numbers.Split(delimiters.ToArray());

		if (values.Any(x => x.Length == 0))
		{
			throw new MultipleSeparatorsException(numbers);
		}

		return values.Select(x => Convert.ToInt32(x));
	}

green. refactor.

	private static IEnumerable<int> GetValues(string numbers)
	{
		var values = SeparateValues(numbers);

		if (values.Any(x => x.Length == 0))
		{
			throw new MultipleSeparatorsException(numbers);
		}

		return values.Select(x => Convert.ToInt32(x));
	}

	private static IEnumerable<string> SeparateValues(string numbers)
	{
		var delimiters = new List<char> { ',', '\n' };
		if (HasDelimiterSection(numbers))
		{
			delimiters.Add(GetDelimiter(numbers));
			numbers = RemoveDelimiterSection(numbers);
		}

		return numbers.Split(delimiters.ToArray());
	}

	private static string RemoveDelimiterSection(string numbers)
	{
		return numbers.Substring(4);
	}

	private static bool HasDelimiterSection(string numbers)
	{
		return numbers.StartsWith("//");
	}

	private static char GetDelimiter(string numbers)
	{
		return numbers[2];
	}

still green. Next requirement.

Calling Add with a negative number will throw an exception “negatives not allowed” followed by the negative that was passed. If there are multiple negatives, show all of them in the exception message.

refactor the tests — mostly naming.

	[SetUp]
	public void BeforeEachTest()
	{
		_delimiter = "";
		_separator = "";
		_expected = 0;
		_numbers = "";
		_calculator = new StringCalculator();
	}

	[Test]
	public void Given_an_empty_input()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(an_empty_input)
			.Should(get_zero)
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_a_single_non_negative_number()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_non_negative_number)
			.Should(get_the_numeric_value_of_the_input)
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_more_than_two_non_negative_numbers_separated_by_a_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(comma_separator)
			.With(an_input_containing_more_than_two_non_negative_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_more_than_two_non_negative_numbers_separated_by_a_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(newline_separator)
			.With(an_input_containing_more_than_two_non_negative_numbers)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_non_negative_numbers_separated_by_comma_and_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_non_negative_number)
			.With(separator_containing_comma_and_newline)
			.With(a_random_non_negative_number)
			.ShouldThrowException<MultipleSeparatorsException>()
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_non_negative_numbers_separated_by_more_than_one_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_non_negative_number)
			.With(separator_containing_more_than_one_comma)
			.With(a_random_non_negative_number)
			.ShouldThrowException<MultipleSeparatorsException>()
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_non_negative_numbers_separated_by_more_than_one_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_non_negative_number)
			.With(separator_containing_more_than_one_newline)
			.With(a_random_non_negative_number)
			.ShouldThrowException<MultipleSeparatorsException>()
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_two_non_negative_numbers_separated_by_a_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_non_negative_number)
			.With(comma_separator)
			.With(a_random_non_negative_number)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_two_non_negative_numbers_separated_by_a_newline()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_non_negative_number)
			.With(newline_separator)
			.With(a_random_non_negative_number)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	[Test]
	public void Given_an_input_containing_two_non_negative_numbers_separated_by_a_space_with_a_space_delimiter()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(space_delimiter)
			.With(a_random_non_negative_number)
			.With(space_separator)
			.With(a_random_non_negative_number)
			.Should(get_the_sum_of_the_numeric_values_in_the_input)
			.Verify();
	}

	private void a_random_non_negative_number()
	{
		int randomValue = _random.Next(5000);
		_numbers += (_numbers.Length > 0 ? _separator : "") + randomValue;
		_expected += randomValue;
	}

	private void an_input_containing_more_than_two_non_negative_numbers()
	{
		for (int i = 0; i < 100; i++)
		{
			a_random_non_negative_number();
		}
	}

	private void separator_containing_comma_and_newline()
	{
		comma_separator();
		newline_separator();
	}

	private void separator_containing_more_than_one_comma()
	{
		comma_separator();
		comma_separator();
	}

	private void separator_containing_more_than_one_newline()
	{
		newline_separator();
		newline_separator();
	}

still green. Now handle the negative number requirement.

	[Test]
	public void Given_an_input_containing_a_single_negative_number()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_negative_number)
			.ShouldThrowException<ArgumentException>("negatives not allowed " + _negativeValues)
			.Verify();
	}

	private void a_random_negative_number()
	{
		int randomValue = _random.Next(-5000, 0);
		_numbers += (_numbers.Length > 0 ? _separator : "") + randomValue;
		_expected += randomValue;
		_negativeValues += (_negativeValues.Length > 0 ? ", " : "") + randomValue;
	}

	[SetUp]
	public void BeforeEachTest()
	{
		_negativeValues = "";
		_delimiter = "";
		_separator = "";
		_expected = 0;
		_numbers = "";
		_calculator = new StringCalculator();
	}

red

STEP 1: a random negative number
WHEN asked to add - FAILED

  negatives not allowed 
  Expected: <System.ArgumentException>
  But was:  null

fix it

	private static IEnumerable<int> GetValues(string numbers)
	{
		var values = SeparateValues(numbers);

		if (values.Any(x => x.Length == 0))
		{
			throw new MultipleSeparatorsException(numbers);
		}

		var integerValues = GetIntegerValues(values);
		var negatives = integerValues.Where(x => x < 0);
		if (negatives.Any())
		{
			throw new ArgumentException ("negatives not allowed "+
				String.Join(", ", negativeValues.Select(x => x.ToString()).ToArray());
		}
		return integerValues;
	}

	private static IEnumerable<int> GetIntegerValues(IEnumerable<string> values)
	{
		return values.Select(x => Convert.ToInt32(x));
	}

green. refactor.

	private static IEnumerable<int> GetValues(string numbers)
	{
		var values = SeparateValues(numbers);

		if (values.Any(x => x.Length == 0))
		{
			throw new MultipleSeparatorsException(numbers);
		}

		var integerValues = GetIntegerValues(values);
		var negatives = integerValues.Where(x => x < 0);
		if (negatives.Any())
		{
			throw new NegativesNotAllowedException (negatives);
		}
		return integerValues;
	}

	private static IEnumerable<int> GetIntegerValues(IEnumerable<string> values)
	{
		return values.Select(x => Convert.ToInt32(x));
	}

public class NegativesNotAllowedException : ArgumentException
{
	public const string NegativesNotAllowedFormat = "negatives not allowed {0}";

	public NegativesNotAllowedException(IEnumerable<int> negativeValues) :
		base(String.Format(NegativesNotAllowedFormat, CombineValuesInString(negativeValues)))
	{
	}

	private static string CombineValuesInString(IEnumerable<int> negativeValues)
	{
		return String.Join(", ", negativeValues.Select(x => x.ToString()).ToArray());
	}
}

	[Test]
	public void Given_an_input_containing_a_single_negative_number()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_negative_number)
			.ShouldThrowException<NegativesNotAllowedException>(String.Format(NegativesNotAllowedException .NegativesNotAllowedFormat, _negativeValues))
			.Verify();
	}

still green. Make sure multiple negatives end up in the exception message.

	[Test]
	public void Given_an_input_containing_two_negative_numbers_separated_by_a_comma()
	{
		Test.Given(_calculator)
			.When(asked_to_add)
			.With(a_random_negative_number)
			.With(comma_separator)
			.With(a_random_negative_number)
			.ShouldThrowException<NegativesNotAllowedException>(String.Format(NegativesNotAllowedException.NegativesNotAllowedFormat, _negativeValues))
			.Verify();
	}

green.

Next requirement… oh man. I didn’t know this was a timed kata! Oh well, I don’t know how much time I’ve put into this because of various interruptions and live blogging it so I’ll stop here.

Read Full Post »

In Test Driven Development (TDD) you write code in incremental steps

  • write a simple test describing the desired behavior of the implementation
  • build out just enough of the implementation that the test can compile
  • run the test and verify that it fails (the test runner generally shows red for this test)
  • implement the desired behavior – just enough to pass the test
  • re-run the test to verify that it passes (the test runner generally shows green for the test)
  • optionally refactor the implementation
  • verify that all tests for the implementation still pass
  • repeat the process with the next desired behavior

This process is often described as red-green-refactor.

You will quickly reach the point where just knowing that a test failed is insufficient information to know what to do about it. One solution to this problem is to modify your process to red-diagnostics-green-refactor (a variation I picked up from Matt Hinze). In the diagnostics step you make sure the test failure message is meaningful. For example, don’t just assert that age is 6. Provide an error message that describes what it means e.g. “received incorrect age when calculating leap year birth date.”

To practice using tests to drive implementation you can search for the word kata and the name of the language you use to code e.g. kata c#. Kata is a Japanese word meaning “formal exercise”. Look for pages where someone has described a kata that you find interesting then break out your tools and see if you can do it using TDD. I find kata that also have an associated step-by-step implementation to be the most educational because I can compare my implementation and TDD process with theirs when I’m done.

A good c# example is James Newkirk’s Stack implementation kata.

If possible use ping-pong style coding in pairs:

  • one person writes a test describing the behavior to be implemented
  • the other implements the behavior
  • swap roles and repeat until all desired behavior has been implemented

If the code should be written defensively consider using an aggressive ping-pong style once the required functionally has been implemented.

  • one person writes a test to try to break the implementation or otherwise cause something unexpected to happen (e.g. null objects, negative values, dates &c out of range, sql injection, etc.)
  • the other person fixes the problem
  • swap roles and repeat

With experience you’ll get to where you can tell that a piece of code could not have been written the way it is if it had been written using TDD.

As you read more kata implementations you’ll also get exposure to the different styles, tools, and frameworks people use to structure their tests, prepare mock interactions with other objects, separate the test implementation details from their description, and reduce the ceremonial aspects of their code.

Write about your experiences so that others can learn from you.

Read Full Post »

In this kata we’ll drive out the implementation of an extension method with the following signature:

public static bool IsNullOrEmpty(this string input, bool trim)

The desired functionality is:

Given an input containing non-whitespace the method should return false.
Given a null input the method should return true.
Given an empty input the method should return true.
Given an input containing only whitespace the method should return true if trim is true, otherwise false.

I’ll be using NUnit with FluentAssert for the test framework .

Let’s start with the simplest test, a null input.

[TestFixture]
public class When_asked_if_a_string_is_null_or_empty_with_optional_trim
{
	[Test]
	public void Given_a_null_string_and_trim_is_false()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(a_null_string)
			.With(trim_set_to_false)
			.Should(return_true)
			.Verify();
	}
}

with test details

	private void asked_if_a_string_is_null_or_empty()
	{
		_result = _input.IsNullOrEmpty(_trim);
	}

	private void a_null_string()
	{
		_input = null;
	}

	private void trim_set_to_false()
	{
		_trim = false;
	}

	private void return_true()
	{
		_result.ShouldBeTrue();
	}

Now stub a failing implementation.

public static class StringExtensions
{
	public static bool IsNullOrEmpty(this string input, bool trim)
	{
		return false;
	}
}

Run the test and get red.

STEP 1: a null string
STEP 2: trim set to false
WHEN asked if a string is null or empty
SHOULD return true - FAILED

fix it

	public static bool IsNullOrEmpty(this string input, bool trim)
	{
		return input == null;
	}

Run the test and get green. Next for completeness, we’ll add a test for the case where trim is true.

	[Test]
	public void Given_a_null_string_and_trim_is_true()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(a_null_string)
			.With(trim_set_to_true)
			.Should(return_true)
			.Verify();
	}

with test details

	private void trim_set_to_true()
	{
		_trim = true;
	}

Run the test and get green. Now let’s try an empty string.

	[Test]
	public void Given_an_empty_string_and_trim_is_false()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(an_empty_string)
			.With(trim_set_to_false)
			.Should(return_true)
			.Verify();
	}

with details

	private void an_empty_string()
	{
		_input = "";
	}

red

STEP 1: an empty string
STEP 2: trim set to false
WHEN asked if a string is null or empty
SHOULD return true - FAILED

fix it

	public static bool IsNullOrEmpty(this string input, bool trim)
	{
		return String.IsNullOrEmpty(input);
	}

Again for completeness let’s add a variant of the test where trim is true.

	[Test]
	public void Given_an_empty_string_and_trim_is_true()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(an_empty_string)
			.With(trim_set_to_true)
			.Should(return_true)
			.Verify();
	}

green. Next we’ll drive out handling inputs that contain whitespace. To get a failure let’s also start with the variant where trim is true.

	[Test]
	public void Given_a_string_containing_only_whitespace_and_trim_is_true()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(a_string_containing_only_whitespace)
			.With(trim_set_to_true)
			.Should(return_true)
			.Verify();
	}

with details

	private void a_string_containing_only_whitespace()
	{
		_input = "\n\r\t ";
	}

red

STEP 1: a string containing only whitespace
STEP 2: trim set to true
WHEN asked if a string is null or empty
SHOULD return true - FAILED

fix it

	public static bool IsNullOrEmpty(this string input, bool trim)
	{
		if (String.IsNullOrEmpty(input))
		{
			return true;
		}
		return trim && String.IsNullOrEmpty(input.Trim());
	}

green. Now to verify the implementation where trim is false.

	[Test]
	public void Given_a_string_containing_only_whitespace_and_trim_is_false()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(a_string_containing_only_whitespace)
			.With(trim_set_to_false)
			.Should(return_false)
			.Verify();
	}

with details

	private void return_false()
	{
		_result.ShouldBeFalse();
	}

green. Finally, let’s write the test for the case where the input contains non-whitespace.

	[Test]
	public void Given_a_string_containing_non_whitespace_and_trim_is_false()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(a_string_containing_non_whitespace)
			.With(trim_set_to_false)
			.Should(return_false)
			.Verify();
	}

with test details

	private void a_string_containing_non_whitespace()
	{
		_input = "aa";
	}

green. And the case where trim is set to true.

	[Test]
	public void Given_a_string_containing_non_whitespace_and_trim_is_true()
	{
		Test.Static()
			.When(asked_if_a_string_is_null_or_empty)
			.With(a_string_containing_non_whitespace)
			.With(trim_set_to_true)
			.Should(return_false)
			.Verify();
	}

Run the test and get green.

That’s it. All desired functionality has been implemented.

Read Full Post »

While Neal Ford was speaking about Emergent Design at the Austin Java Users Group tonight he showed a snippet of TDD code he wrote to find Perfect numbers (Wikipedia). I wondered how I might solve it using LINQ (and FluentAssert).

[TestFixture]
public class When_looking_for_Perfect_numbers
{
	[Test]
	public void Given_a_non_perfect_number()
	{
		Test.Given(_finder)
			.When(asked_if_a_number_is_perfect)
			.With(a_non_perfect_number)
			.Should(return_false)
			.Verify();
	}

	private void asked_if_a_number_is_perfect()
	{
		_result = _finder.IsPerfectNumber(_candidate);
	}

	private void a_non_perfect_number()
	{
		_candidate = 3;
	}

	private void return_false()
	{
		_result.ShouldBeFalse();
	}
}

make it compile

public class PerfectNumberFinder
{
	public bool IsPerfectNumber(int candidate)
	{
		return true;
	}
}

red

  WITH a non perfect number
  WHEN asked if a number is perfect
  SHOULD return false - FAILED

green (passive aggressive)

	public bool IsPerfectNumber(int candidate)
	{
		return false;
	}

Next test:

	[Test]
	public void Given_a_perfect_number()
	{
		Test.Given(_finder)
			.When(asked_if_a_number_is_perfect)
			.With(a_perfect_number)
			.Should(return_true)
			.Verify();
	}

	private void a_perfect_number()
	{
		_candidate = 6;
	}

	private void return_true()
	{
		_result.ShouldBeTrue();
	}

red

  WITH a perfect number
  WHEN asked if a number is perfect
  SHOULD return true - FAILED

Based on the Wikipedia article, getting to green is going to require more tools. Specifically we need a way to get the positive factors of an integer.

[TestFixture]
public class When_getting_the_factors_of_an_integer
{
	[Test]
	public void Given_6()
	{
		Test.Static()
			.When(asked_to_get_positive_factors)
			.With(the_number_6)
			.Should(get_the_correct_factors)
			.Verify();
	}

	private void asked_to_get_positive_factors()
	{
		_result = _input.GetPositiveFactors();
	}

	private void the_number_6()
	{
		_input = 6;
		_expected = new[] { 1, 2, 3, 6 };
	}

	private void get_the_correct_factors()
	{
		_result.OrderBy(x=>x).ShouldContainAllInOrder(_expected);
	}
}

make it compile

public static class IntExtensions
{
	public static IEnumerable<int> GetPositiveFactors(this int input)
	{
		return new int[]{};
	}
}

red

WITH the number 6
WHEN asked to get positive factors
SHOULD get the correct factors - FAILED

green

	public static IEnumerable<int> GetPositiveFactors(this int input)
	{
		return Enumerable.Range(1, (int)Math.Ceiling(Math.Sqrt(input)))
			.Where(potentialFactor => input % potentialFactor == 0)
			.SelectMany(factor => GetFactorPair(input, factor))
			.Distinct();
	}

	private static IEnumerable<int> GetFactorPair(int candidate, int factor)
	{
		yield return candidate / factor;
		yield return factor;
	}

We should also add a guard against negative inputs:

	[Test]
	public void Given_a_negative_value()
	{
		Test.Static()
			.When(asked_to_get_positive_factors)
			.With(a_negative_value)
			.Should(get_an_empty_result)
			.Verify();
	}

	private void a_negative_value()
	{
		_input = -25;
	}

	private void get_an_empty_result()
	{
		_result.Count().ShouldBeEqualTo(0);
	}

red

System.ArgumentOutOfRangeException : Specified argument was out of the range of valid values.

Indeed…. fix it:

	public static IEnumerable<int> GetPositiveFactors(this int input)
	{
		if (input < 1)
		{
			return new int[] { };
		}
		return Enumerable.Range(1, (int)Math.Ceiling(Math.Sqrt(input)))
			.Where(potentialFactor => input % potentialFactor == 0)
			.SelectMany(factor => GetFactorPair(input, factor))
			.Distinct();
	}

green

Now use the extension method to make our PerfectNumberFinder test pass:

	public bool IsPerfectNumber(int candidate)
	{
		return candidate * 2 == candidate.GetPositiveFactors().Sum();
	}

green

One other thing we might like is a list of Perfect numbers.

[TestFixture]
public class When_generating_Perfect_numbers
{
	[SetUp]
	public void BeforeEachTest()
	{
		_finder = new PerfectNumberFinder();
	}

	[Test]
	public void Given_a_maximum_value_of_1000()
	{
		Test.Given(_finder)
			.When(asked_for_perfect_numbers)
			.With(a_maximum_value_of_1000)
			.Should(not_exceed_the_maximum_value)
			.Should(get_the_set_of_perfect_numbers)
			.Verify();
	}

	private void a_maximum_value_of_1000()
	{
		_maxValue = 1000;
		_expected = new[] { 6, 28, 496 };
	}

	private void asked_for_perfect_numbers()
	{
		_result = _finder.GetPerfectNumbers(_maxValue);
	}

	private void get_the_set_of_perfect_numbers()
	{
		_result.OrderBy(x => x).ShouldContainAllInOrder(_expected);
	}

	private void not_exceed_the_maximum_value()
	{
		_result.Max().ShouldBeLessThan(_maxValue);
	}
}

make it compile

public class PerfectNumberFinder
{
	public IEnumerable<int> GetPerfectNumbers(int stopAt)
	{
		return new int[] { };
	}

red

WITH a maximum value of 1000
WHEN asked for perfect numbers
SHOULD not exceed the maximum value - FAILED

System.InvalidOperationException : Sequence contains no elements

Hmm. Should I put another assertion on the test or change the implementation? I think another assertion is in order as it is possible there are no Perfect numbers within the range (i.e. given a value less than 6).

	[Test]
	public void Given_a_maximum_value_of_1000()
	{
		Test.Given(_finder)
			.When(asked_for_perfect_numbers)
			.With(a_maximum_value_of_1000)
			.Should(not_get_an_empty_result)
			.Should(not_exceed_the_maximum_value)
			.Should(get_the_set_of_perfect_numbers)
			.Verify();
	}

	private void not_get_an_empty_result()
	{
		_result.Count().ShouldBeGreaterThan(0);
	}

red

WITH a maximum value of 1000
WHEN asked for perfect numbers
SHOULD not get an empty result - FAILED

green

	public IEnumerable<int> GetPerfectNumbers(int stopAt)
	{
		return Enumerable.Range(1, stopAt)
			.Where(IsPerfectNumber);
	}

This works for the first four Perfect numbers. Beyond that we need a faster way to skip the values that cannot be Perfect numbers. The Wikipedia article offers some good insight along those lines if you are interested.

Read Full Post »

Consider the following problem: We have two integer ranges (1,5) and (10,15) and we want to find the range of the gap between them, if any (6,9).

Lets start with a new project and add references to a test framework (NUnit) and a BDD framework (FluentAssert)

We’ll start with a simple container to hold an integer range:

public class Range
{
	public int Start;
	public int End;
}

Next we’ll stub the range gap finder

public class RangeGapFinder
{
}

and our test fixture

[TestFixture]
public class When_asked_for_the_gap_between_two_ranges
{
	
}

Now we can start driving out the implementation. First off, let’s check the happy path – a gap between the ranges:

	[Test]
	public void Given_a_value_gap()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(A_value_gap)
			.Should(Get_a_non_null_result)
			.Should(Get_the_correct_range_start_value)
			.Should(Get_the_correct_range_end_value)
			.Verify();
	}

now to add the test parts

	public void A_value_gap()
	{
		_range1 = new Range(1, 5);
		_range2 = new Range(10, 15);
		_expectedStart = 6;
		_expectedEnd = 9;
	}

	private void Asked_to_find_the_gap(RangeGapFinder rangeGapFinder)
	{
		_gap = rangeGapFinder.FindGap(_range1, _range2);
	}

	private void Get_a_non_null_result()
	{
		_gap.ShouldNotBeNull();
	}

	private void Get_the_correct_range_start_value()
	{
		_gap.Start.ShouldBeEqualTo(_expectedStart);
	}

	private void Get_the_correct_range_end_value()
	{
		_gap.End.ShouldBeEqualTo(_expectedEnd);
	}

which implies this method stub

public class RangeGapFinder
{
	public Range FindGap(Range range1, Range range2)
	{
		return null;
	}
}

and requires a constructor for the Range container

public class Range
{
	public int Start;
	public int End;

	public Range(int start, int end)
	{
		Start = start;
		End = end;
	}
}

Run the test. It fails. Now change the implementation so that the test passes.

	public Range FindGap(Range range1, Range range2)
	{
		return new Range(range1.End + 1, range2.Start - 1);
	}

That takes care of the happy path. Now what should happen if the ranges overlap (there is no gap) e.g. (1,5) and (2,9)? Let’s say the implementation should return null if there is no gap. First the failing test:

	[Test]
	public void Given_second_range_starts_in_the_first_range()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(Second_range_starting_in_the_first_range)
			.Should(Get_a_null_result)
			.Verify();
	}

	private void Second_range_starting_in_the_first_range()
	{
		_range1 = new Range(1, 5);
		_range2 = new Range(2, 9);
	}

	private void Get_a_null_result()
	{
		_gap.ShouldBeNull();
	}

Run the test and it fails. Now to update the implementation.

	public Range FindGap(Range range1, Range range2)
	{
		if (range1.End > range2.Start)
		{
			return null; // no gap
		}

		return new Range(range1.End + 1, range2.Start - 1);
	}

That covers the overlapping no-gap case. So now let’s think about inputs that could break the code. The first case is if the end value of range1 is the same as the start value of range2 e.g. (1,5) and (5,9). There is no gap so it should return null right?

	[Test]
	public void Given_first_range_End_value_equal_to_second_range_Start_value()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(First_range_End_value_same_as_second_range_Start_value)
			.Should(Get_a_null_result)
			.Verify();
	}

	private void First_range_End_value_same_as_second_range_Start_value()
	{
		_range1 = new Range(1, 5);
		_range2 = new Range(5, 9);
	}

Run the test and it fails! The fix is simple enough:

	public Range FindGap(Range range1, Range range2)
	{
		if (range1.End >= range2.Start)
		{
			return null; // no gap
		}

		return new Range(range1.End + 1, range2.Start-1);
	}

Okay. Another no-gap case is when the two ranges meet but do not overlap e.g. (1,5) (6,10). That should return null too. Does it?

	[Test]
	public void Given_adjacent_ranges()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(Adjacent_ranges)
			.Should(Get_a_null_result)
			.Verify();
	}

	private void Adjacent_ranges()
	{
		_range1 = new Range(1, 5);
		_range2 = new Range(6, 10);
	}

Run the test and it fails. The fix is another adjustment to the guard clause:

	public Range FindGap(Range range1, Range range2)
	{
		if (range1.End + 1 >= range2.Start)
		{
			return null; // no gap
		}

		return new Range(range1.End + 1, range2.Start - 1);
	}

What else could happen? The user could call the method with the ranges out of order e.g. (6,10) and (1,5). In this case let’s just do what the user expected instead of throwing an exception.

	[Test]
	public void Given_range2_Start_value_lower_than_range1_Start_value()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(Second_range_Start_lower_than_first_range_Start)
			.Should(Get_a_non_null_result)
			.Should(Get_the_correct_range_start_value)
			.Should(Get_the_correct_range_end_value)
			.Verify();
	}

	private void Second_range_Start_lower_than_first_range_Start()
	{
		_range1 = new Range(10, 15);
		_range2 = new Range(1, 5);
		_expectedStart = 6;
		_expectedEnd = 9;
	}

Test fails. Now to fix it we’ll just recurse:

	public Range FindGap(Range range1, Range range2)
	{
		if (range2.Start < range1.Start)
		{
			return FindGap(range2, range1);
		}
		if (range1.End + 1 >= range2.Start)
		{
			return null; // no gap
		}

		return new Range(range1.End + 1, range2.Start - 1);
	}

What else could happen? The user might send the start and end values swapped. A range is a range so let’s do the expected thing there too, starting with range1:

	[Test]
	public void Given_range1_End_value_lower_than_its_Start_value()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(Range1_end_value_lower_than_its_start_value)
			.Should(Get_a_non_null_result)
			.Should(Get_the_correct_range_start_value)
			.Should(Get_the_correct_range_end_value)
			.Verify();
	}

	private void Range1_end_value_lower_than_its_start_value()
	{
		_range1 = new Range(5, 1);
		_range2 = new Range(10, 15);
		_expectedStart = 6;
		_expectedEnd = 9;
	}

The test fails. We could simply update the Range

	if (range1.End < range1.Start)
	{
		int temp = range1.End;
		range1.End = range1.Start;
		range1.Start = range1.End;
	}

but that introduces a side effect. Let’s update the test to take the potential side effect into account:

	[Test]
	public void Given_range1_End_value_lower_than_its_Start_value()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(Range1_end_value_lower_than_its_start_value)
			.Should(Get_a_non_null_result)
			.Should(Get_the_correct_range_start_value)
			.Should(Get_the_correct_range_end_value)
			.Should(Not_change_the_values_of_range1)
			.Should(Not_change_the_values_of_range2)
			.Verify();
	}

	private void Not_change_the_values_of_range1()
	{
		_range1.Start.ShouldBeEqualTo(_originalRange1.Start);
		_range1.End.ShouldBeEqualTo(_originalRange1.End);
	}

	private void Not_change_the_values_of_range2()
	{
		_range2.Start.ShouldBeEqualTo(_originalRange2.Start);
		_range2.End.ShouldBeEqualTo(_originalRange2.End);
	}

	private void Asked_to_find_the_gap(RangeGapFinder rangeGapFinder)
	{
		_originalRange1 = new Range(_range1.Start, _range1.End);
		_originalRange2 = new Range(_range2.Start, _range2.End);
		_gap = rangeGapFinder.FindGap(_range1, _range2);
	}

Now the fix is create a new Range with the points swapped.

	public Range FindGap(Range range1, Range range2)
	{
		if (range2.Start < range1.Start)
		{
			return FindGap(range2, range1);
		}

		if (range1.End < range1.Start)
		{
			range1 = new Range(range1.End, range1.Start);
		}

		if (range1.End + 1 >= range2.Start)
		{
			return null; // no gap
		}

		return new Range(range1.End + 1, range2.Start - 1);
	}

Next we do the same for range2:

	[Test]
	public void Given_range2_End_value_lower_than_its_Start_value()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(Range2_end_value_lower_than_its_start_value)
			.Should(Get_a_non_null_result)
			.Should(Get_the_correct_range_start_value)
			.Should(Get_the_correct_range_end_value)
			.Should(Not_change_the_values_of_range1)
			.Should(Not_change_the_values_of_range2)
			.Verify();
	}

	private void Range2_end_value_lower_than_its_start_value()
	{
		_range1 = new Range(1, 5);
		_range2 = new Range(15, 10);
		_expectedStart = 6;
		_expectedEnd = 9;
	}

and implement the fix:

	public Range FindGap(Range range1, Range range2)
	{
		if (range2.Start < range1.Start)
		{
			return FindGap(range2, range1);
		}

		if (range1.End < range1.Start)
		{
			range1 = new Range(range1.End, range1.Start);
		}	
		
		if (range2.End < range2.Start)
		{
			range2 = new Range(range2.End, range2.Start);
		}

		if (range1.End + 1 >= range2.Start)
		{
			return null; // no gap
		}

		return new Range(range1.End + 1, range2.Start - 1);
	}

One last thing. We need some null checks on the inputs. Instead of retuning null we’ll make these exceptional cases. We could just let the first use trigger a NullReferenceException but a more meaningful exception might be nice.

	[Test]
	public void Given_a_null_for_range1()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(A_null_range1)
			.ShouldThrowException<ArgumentNullException>("range1 cannot be null")
			.Verify();
	}

	private void A_null_range1()
	{
		_range1 = null;
		_range2 = new Range(1, 5);
	}

	private void Asked_to_find_the_gap(RangeGapFinder rangeGapFinder)
	{
		if (_range1 != null)
		{
			_originalRange1 = new Range(_range1.Start, _range1.End);
		}
		_originalRange2 = new Range(_range2.Start, _range2.End);
		_gap = rangeGapFinder.FindGap(_range1, _range2);
	}
	public Range FindGap(Range range1, Range range2)
	{
		if (range1 == null)
		{
			throw new ArgumentNullException("range1","range1 cannot be null");
		}
	...

And the same for range2:

	[Test]
	public void Given_a_null_for_range2()
	{
		Test.Given(new RangeGapFinder())
			.When(Asked_to_find_the_gap)
			.With(A_null_range2)
			.ShouldThrowException<ArgumentNullException>("range2 cannot be null")
			.Verify();
	}

	private void A_null_range2()
	{
		_range1 = new Range(1, 5);
		_range2 = null;
	}

	private void Asked_to_find_the_gap(RangeGapFinder rangeGapFinder)
	{
		if (_range1 != null)
		{
			_originalRange1 = new Range(_range1.Start, _range1.End);
		}
		if (_range2 != null)
		{
			_originalRange2 = new Range(_range2.Start, _range2.End);
		}
		_gap = rangeGapFinder.FindGap(_range1, _range2);
	}

with final result:

	public Range FindGap(Range range1, Range range2)
	{
		if (range1 == null)
		{
			throw new ArgumentNullException("range1","range1 cannot be null");
		}

		if (range2 == null)
		{
			throw new ArgumentNullException("range2", "range2 cannot be null");
		}

		if (range2.Start < range1.Start)
		{
			return FindGap(range2, range1);
		}

		if (range1.End < range1.Start)
		{
			range1 = new Range(range1.End, range1.Start);
		}	
		
		if (range2.End < range2.Start)
		{
			range2 = new Range(range2.End, range2.Start);
		}

		if (range1.End + 1 >= range2.Start)
		{
			return null; // no gap
		}

		return new Range(range1.End + 1, range2.Start - 1);
	}

That’s it.

Read Full Post »

« Newer Posts