Feeds:
Posts
Comments

Archive for the ‘TDD’ Category

I’ve been using FluentAssert for my test-driven development because it abstracts away the test details leaving only a highly readable test. I also find that it enforces a certain level of separation of concerns and moistness in the tests without spreading portions of the tests through an inheritance stack. For example:

[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();
	}

Today I was reading about features of the Factor language, specifically the part about naming code not values and that it is stack based instead of function based, and it occurred to me that a DSL that has no parameters is a lot like a contactenative language except for the extra visual bits like . and () joining the method calls together. This started me to thinking how I might be able to further reduce the ceremonial aspects of the test framework.

One nice thing about a DSL is it can be coded to be context sensitive, meaning it might not let you put an assertion call (e.g. Should()) before an arranging call (e.g. With()). A lower ceremony framework wouldn’t necessarily offer that level of protection but in return we might get tests that are even easier to read and that don’t have an execution command appended. That’s one thing that bothers me about the current framework, the command of execution, .Verify(), is required at the end of each test chain in order to fire off the framework test runner. If you leave it off the NUnit test runs but the BDD test doesn’t and you get a false green. I wondered if I could address that annoyance as well as make something more concatenative. Here’s the result:

[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.Verify(
			with_a_null_string,
			with_trim_set_to_false,
			when_asked_if_a_string_is_null_or_empty,
			should_return_true
			);
	}
}

Instead of chaining methods with Action parameters we’re passing all the Actions as a params array. The DSL methods .When(part_A) .Should(part_B) etc. are simply replaced with their parameters part_A, part_B but we can change the Action names to include the When, Should etc. in order to retain readability in the test and its output. The call to Verify becomes the only framework method we call and we can give it the test Actions to run in the order we want to run them instead of having to give the Action for the method under test (When()) first – this is about as close as I think I can get to a Forth style Word command line in C#.

I’ve integrated this variation with the existing test result reporting so if you start your Action names with known keywords you’ll continue to get uppercased keyword output, e.g.:

WITH a null string
WITH trim set to false
WHEN asked if a string is null or empty
SHOULD return true - FAILED

  Expected: True
  But was:  False

I’ve added this as an alternative DSL path in the FluentAssert framework on GitHub if you want to try it out.

Enjoy!

Advertisements

Read Full Post »

This is a repeatable Test Driven Development coding exercise that can be used to hone your test-first, red-green-refactor programming skills.

Structural Validation 2

Start with an input validator and the method:

bool IsValid(string input)

This method will take a string input and return true if the string contains an input that is structurally correct according to the rules below, otherwise it will return false.

There are seven parts grouped by similar rules. You might choose to do one part a day, each day building on the previous day’s implementation.

Implement support for each of the rules in order and don’t read ahead. The rules are intended to build upon one another without contradiction i.e. once a rule makes the input invalid no other rule can make it valid again – the reverse is not true. Remember to refactor between tests if necessary.

  1. The input is invalid if …
    1. it is empty
    2. it contains uppercase letters
    3. it contains numbers
    4. it contains symbols (other than period)
    5. it contains characters other than lower-case letters [a-z] and period (.)
    6. it contains more vowels (aeiou) than consonants e.g. amazonian
    7. the difference between the total number of consonants and the total number of vowels is greater than 1 e.g. anthology
    8. it contains only periods (.)
    9. it has more than 9 characters
    10. it has less than 9 characters

    my implementations: BDD style, NUnit only

  2. Consonants. The input is invalid if …
    1. the first three characters are consonants and the next three characters are vowels e.g. btreeinfo
    2. the first three characters are consonants and the last three characters are vowels e.g. mysteriou
    3. the middle three characters are consonants and the last three characters are vowels e.g. hashtreei
    4. the first three characters are consonants and there are an equal number of consonants and vowels e.g. three.eat
    5. the middle three characters are consonants and there are an equal number of consonants and vowels e.g .airbrain
    6. the last three characters are consonants and there are an equal number of consonants and vowels e.g. air.eight
    7. the first, middle, and last characters (positions 1, 5, 9) are consonants and there are an equal number of consonants and vowels e.g. bee.steak
  3. Vowels. The input is invalid if …
    1. the first three characters are vowels and the next three characters are consonants e.g. uuinstall
    2. the first three characters are vowels and the last three characters are consonants e.g. eairworth
    3. the middle three characters are vowels and the last three characters are consonants e.g. seriously
    4. the first three characters are vowels and there are more consonants than vowels e.g. eau.my.my
    5. the middle three characters are vowels and there are more consonants than vowels e.g. m.beaux.y
    6. the last three characters are vowels and there are more consonants than vowels e.g. my.my.eau
    7. the first, middle, and last characters are vowels and there are more consonants than vowels e.g. attribute
  4. Apply the following transform to the input 741852963 . The digits represent the position (1-based) of the character in the original input. For example. If the input was yarmulkes the result would be kmyeuaslr . – The input is invalid if …
    1. the transformed input fails Part 2 rule 1 e.g. loaderror
    2. the transformed input fails Part 2 rule 2 e.g. moondance
    3. the transformed input fails Part 2 rule 3 e.g. existence
    4. the transformed input fails Part 2 rule 4 e.g. coal.oxes
    5. the transformed input fails Part 2 rule 5 e.g. after.ale
    6. the transformed input fails Part 2 rule 6 e.g. appear.in
    7. the transformed input fails Part 2 rule 7 e.g. air.lanes
  5. This input is invalid if …
    1. the transformed input fails Part 3 rule 1 e.g. otherwise
    2. the transformed input fails Part 3 rule 2 e.g. afternoon
    3. the transformed input fails Part 3 rule 3 e.g. dangerous
    4. the transformed input fails Part 3 rule 4 e.g. in.ache.r
    5. the transformed input fails Part 3 rule 5 e.g. no.man.in
    6. the transformed input fails Part 3 rule 6 e.g. p.anti.no
    7. the transformed input fails Part 3 rule 7 e.g. guitarist
  6. From here on any input that isn’t explicitly valid is invalid. The input is valid if it passes all rules in parts 1-5 and any one of the following:
    1. the first three characters are all consonants e.g. pyromania
    2. the middle three characters are all consonants e.g. anonymous
    3. the last three characters are all consonants e.g. aquaducts
    4. the first, middle, and last characters are all consonants e.g. bluejeans
    5. the first three characters are all vowels e.g. aaah.hhha
    6. the middle three characters are all vowels e.g. h.haaa..h
    7. the last three characters are all vowels e.g. ahh.hhaaa
    8. the first, middle, and last characters are all vowels e.g. ahh.ahhaa
  7. apply Part 6 rules to the transformed input. The input is valid if it passes all rules in parts 1-5 and any one of the following:
    1. the transformed input passes Part 6 rule 1 e.g. gladiator
    2. the transformed input passes Part 6 rule 2 e.g. invisible
    3. the transformed input passes Part 6 rule 3 e.g. genealogy
    4. the transformed input passes Part 6 rule 4 e.g. bonedevil
    5. the transformed input passes Part 6 rule 5 e.g. aahah.ahh
    6. the transformed input passes Part 6 rule 6 e.g. hah.aahah
    7. the transformed input passes Part 6 rule 7 e.g. hhaa.ahha
    8. the transformed input passes Part 6 rule 8 e.g. haa.ahahh

Verify that the word celestial is a valid input.
Verify that the word tictactoe is an invalid input.

The code you have written should return true for legal, winning tic-tac-toe boards using the characters x, o, and period(.) for empty squares. The rules assume x moves first. The code should also return false given inputs that represent impossible, incomplete, and tie games.

Read Full Post »

This is a repeatable Test Driven Development coding exercise that can be used to hone your test-first, red-green-refactor programing skills.

Credit card number validator

Start with a simple card number validator. Add the method:

bool IsValid(string number)

This method will take a string input and return true if the string contains a structurally correct credit card number, otherwise it will return false.

Implement support for each of the statements below in order and don’t read ahead. Remember to refactor between tests if necessary.

  1. An empty card number is invalid.
  2. Card numbers contain only digits.
  3. A card number cannot contain more than 16 digits.
  4. A card number cannot contain less than 13 digits.

    The last digit of the card number is a check digit. The check digit is determined by using all the digits in the card number including the check digit to calculate a sum. This calculated value is the checksum.

  5. When the card number is prefixed with zeros to be exactly 16 digits long the checksum includes the sum of all digits that have an odd index (0-based).

    For example, given the card number 0101010101010101 the checksum would be the sum of the 1’s (i.e. 1*8 = 8 ) because these digits are all at odd-numbered indexes into the card number.

  6. When the card number is prefixed with zeros to be exactly 16 digits long the checksum also includes the sum of the substitution digits for all card number digits that have an even index (0-based). Use the card number digit as an index into “0246813579” to get the substitution digit to add to the checksum.

    For example, given the card number 4060101010101010 the checksum would include the sum of the substitution digits for 4 and 6 and the 1’s because these digits are all at even-numbered indexes into the card number. The substitution values added to the checksum in this case would be 8 (substitution digit at index 4 is 8 ) and 3 (substitution digit at index 6 is 3) and six 2’s (substitution digit at index 1 is 2) – i.e. 8 + 3 + 6*2 = 23.

  7. The last digit of the checksum for a valid card number must be zero.

    For example, a checksum of 8 + 23 = 31 is invalid. This means the card number 4161111111111111 is structurally invalid. Card number 4566111111111111 has a checksum of 5+6+6*1+23 = 17+23 = 40. The last digit of this checksum is zero so this card number is structurally valid.

Lastly, verify that one of your own credit card numbers IsValid() according the code you have produced.

Read Full Post »

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 »

« Newer Posts - Older Posts »

%d bloggers like this: