This project is read-only.

Retrofitting StoryQ onto existing unit tests

StoryQ is great for BDD-ifying existing unit tests, and that's what this page explains how to do - however it's not always a good idea to try and retrofit BDD onto existing unit tests. BDD is an agile methodology that is all about multi-stakeholder communication, and usually tests things outside-in (while a lot of unit tests are at the slight lower "unit" level). That said, if you know what you're doing, here's how you retrofit StoryQ onto a unit test:

The original unit test

Here's a simple unit test that checks how well a System.Collections.Generic.Dictionary with string keys works in case insensitive mode (using StringComparer.InvariantCultureIgnoreCase). We are testing a system class here for demonstration purposes only, usually your own code will be the system under test.

[TestMethod]
public void CaseInsensitiveRetrievalFromDictionary()
{
    Dictionary<string, int> d = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
    d["five"] = 5;
    Assert.IsTrue(d.ContainsKey("FIVE"));
    Assert.AreEqual(5, d["FIVE"]);
}

1) Identify the Given / When / Then sections of your unit test

Given, When and Then are equivalent to Arrange, Act and Assert. If you are lucky, your unit test will already be in that order (whether by design or naturally).
Put comments into your test that separate chunks of code into "given"s, "when"s and "then"s (use "and" if there's multiple chunks), and put in a human-friendly description of these cases. In this case each chunk is a single line, but in many cases the stages of your test may span many lines:

[TestMethod]
public void CaseInsensitiveRetrievalFromDictionary()
{
    // Given a case insenstive dictionary
    Dictionary<string, int> d = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);

    // When I put 5 into the dictionary with a key of "five"
    d["five"] = 5;

    // Then the dictionary should contain the key "FIVE"
    Assert.IsTrue(d.ContainsKey("FIVE"));

    // And the value in the dictionary for "FIVE" should be 5
    Assert.AreEqual(5, d["FIVE"]);
}

2) Extract steps into methods

Each chunk of code can be expressed as a narrative. In StoryQ, each narrative is expressed as a method. So you just need to extract each chunk into a method. You can use Visual Studio's extract method tool (Ctrl+R, Ctrl+M) (or your favourite refactoring plugin) to do this. Note that you'll have to promote the dictionary into a class variable so that all the methods can access it. Also: you should leave off the prefixes "Given", When", "Then" and "And":

private Dictionary<string, int> _d;

[TestMethod]
public void TestCaseInsensitiveRetrievalFromDictionary()
{
    // Given a case insenstive dictionary
    ACaseInsensitiveDictionary();

    // When i put 5 into the dictionary with a key of "five"
    IPutSomethingIntoTheDictionaryWithALowerCaseKey();

    // Then the dictionary should contain the key "FIVE"
    TheDictionaryShouldContainTheUpperCaseKey();

    // And the value in the dictionary for "FIVE" should be 5
    TheValueInTheDictionaryForTheUpperCaseKeyShouldBeTheSame();
}

private void ACaseInsensitiveDictionary()
{
    _d = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
}

private void IPutSomethingIntoTheDictionaryWithALowerCaseKey()
{
    _d["five"] = 5;
}

private void TheDictionaryShouldContainTheUpperCaseKey()
{
    Assert.IsTrue(_d.ContainsKey("FIVE"));
}

private void TheValueInTheDictionaryForTheUpperCaseKeyShouldBeTheSame()
{
    Assert.AreEqual(5, _d["FIVE"]);
}

3) Wrap the narrative method calls in StoryQ's warm embrace

Because each of these methods takes no arguments and returns void, you can pass them into StoryQ whenever StoryQ expects an executable narrative. First, however, we have to write the non-executable part of the Story. This is an extremely important step in BDD, because it documents the justification for the feature being tested:

    new Story("Custom IEqualityComparers on .NET Dictionaries")
        .InOrderTo("Retrieve data from a dictionary regardless of the string key's case")
        .AsA("Developer")
        .IWant("To create a dictionary with a case-insensitive IEqualityComparer")
        .WithScenario("Storing and Retrieving values with StringComparer.InvariantCultureIgnoreCase")
Once this is provided, we should start using the narrative methods as arguments to the StoryQ fluent interface. Start by replacing:

// Given a case insenstive dictionary
ACaseInsensitiveDictionary();
with:

.Given(ACaseInsensitiveDictionary)

You should end up with the following StoryQ test method. Don't forget to call Execute at the end:

[TestMethod]
public void TestCaseInsensitiveRetrievalFromDictionary()
{
    new Story("Custom IEqualityComparers on .NET Dictionaries")
        .InOrderTo("Retrieve data from a dictionary regardless of the string key's case")
        .AsA("Developer")
        .IWant("To create a dictionary with a case-insensitive IEqualityComparer")
        .WithScenario("Storing and Retrieving values with StringComparer.InvariantCultureIgnoreCase")
        .Given(ACaseInsensitiveDictionary)
        .When(IPutSomethingIntoTheDictionaryWithALowerCaseKey)
        .Then(TheDictionaryShouldContainTheUpperCaseKey)
        .And(TheValueInTheDictionaryForTheUpperCaseKeyShouldBeTheSame)
        .Execute();
}

Running this should produce the nicely formatted results:

Story is Custom IEqualityComparers on .NET Dictionaries
  In order to Retrieve data from a dictionary regardless of the string key's case
  As a Developer
  I want To create a dictionary with a case-insensitive IEqualityComparer

      With scenario Storing and Retrieving values with StringComparer.InvariantCultureIgnoreCase
        Given a case insensitive dictionary                                                      => Passed
        When I put something into the dictionary with a lower case key                           => Passed
        Then the dictionary should contain the upper case key                                    => Passed
          And the value in the dictionary for the upper case key should be the same              => Passed

4) Parameterize your Narrative methods

A final step that you might like to take is to make your Narrative methods accept parameters. This makes them more reusable, and can improve the readability. We've show an example where the method TheDictionaryShouldContainTheKey_ is reused:

private Dictionary<string, int> _d;

[TestMethod]
public void TestCaseInsensitiveRetrievalFromDictionary()
{
    new Story("Custom IEqualityComparers on .NET Dictionaries")
        .InOrderTo("Retrieve data from a dictionary regardless of the string key's case")
        .AsA("Developer")
        .IWant("To create a dictionary with a case-insensitive IEqualityComparer")
        .WithScenario("Storing and Retrieving values with StringComparer.InvariantCultureIgnoreCase")
        .Given(ACaseInsensitiveDictionary)
        .When(IPut_IntoTheDictionaryWithAKeyOf_, 5, "five")
        .Then(TheDictionaryShouldContainTheKey_, "FIVE")
        .And(TheDictionaryShouldContainTheKey_, "FiVe")
        .And(TheValueInTheDictionaryFor_ShouldBe_, "FIVE", 5)
        .Execute();
}

private void TheValueInTheDictionaryFor_ShouldBe_(string key, int expected)
{
    Assert.AreEqual(expected, _d[key]);
}

private void TheDictionaryShouldContainTheKey_(string key)
{
    Assert.IsTrue(_d.ContainsKey(key));
}

private void IPut_IntoTheDictionaryWithAKeyOf_(int value, string key)
{
    _d[key] = value;
}

private void ACaseInsensitiveDictionary()
{
    _d = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
}

This will result in the following output:

Story is Custom IEqualityComparers on .NET Dictionaries
  In order to Retrieve data from a dictionary regardless of the string key's case
  As a Developer
  I want To create a dictionary with a case-insensitive IEqualityComparer

      With scenario Storing and Retrieving values with StringComparer.InvariantCultureIgnoreCase
        Given a case insensitive dictionary                                                      => Passed
        When I put 5 into the dictionary with a key of five                                      => Passed
        Then the dictionary should contain the key FIVE                                          => Passed
          And the dictionary should contain the key FiVe                                         => Passed
          And the value in the dictionary for FIVE should be 5                                   => Passed


Hopefully, that kind of output is exactly what you're looking for. It's our belief that this is a pretty easy process to go through after a couple of attempts - at least when you've got a good "extract method" tool.


If you want to see the effect of a failing test, just change the dictionary declaration to a case-sensitive one:

private void ACaseInsensitiveDictionary()
{
    _d = new Dictionary<string, int>();
}

Go back to the home page to continue reading

Last edited Jan 25, 2010 at 12:35 PM by robfe, version 5

Comments

No comments yet.