Saturday, August 08, 2009

Misadventures In BDD With NBehave

From my previous post, you might have picked up that I like the idea of BDD's given-when-then construct for describing scenarios.

For a while, I've been wanting to use this syntax in unit/integration tests to make them clearer. But I've been hoping to do that without switching languages or unit-testing frameworks. Toward that end, I tried out NBehave.

Here's some test code that demonstrates my worst fears about why NBehave might not be right for me:

public void Execute()
{
    var story = new Story("Search Contacts by Relationship Strength")
        .AsA("User")
        .IWant("To search on the Strength field")
        .SoThat("I can find Contacts who have a given Strength");

    Scenario scenario = story
        .WithScenario("Find Contacts with Strength of 50");

    scenario.Given("The database contains a Contact with a Strength of 50")
        .And("a search results view", GivenResultsInfo)
        .And("a query for Contacts with Strength of 50",
            GivenQueryForContactsWithStrengthOf50)
        .When("The search is run", WhenRunSearch)
        .Then("The results contain Contacts with Strength of 50",
            ThenShouldContainResultsWithStrengthOf50);
}

private void GivenResultsInfo()
{
    var resultsView = new ReadOnlyResultsView(
        ExtendableEntity.Person, SortType.Ascending, 
        new[] { Column }, Column);
    ResultsInfo = new ResultsInfo(resultsView);
}
private void GivenQueryForContactsWithStrengthOf50()
{
    var condition = 
        new DecimalCondition(Column, ComparisonOperation.EqualTo);
    condition.DataField.Value = 5;

    _query = TypeHelper.GetAdvancedQuery(_entityType);
    _listManager.AddListToAdvancedQuery(_query);
    _query.MainConditionGroup.AddCondition(condition);
}
private void WhenRunSearch()
{
    ISearchService service = TypeHelper.GetSearchService(_entityType);
    ISearchResults results = service.GetResults(_query, ResultsInfo);
    _resultsData = results.Results;
}
private void ThenShouldContainResultsWithStrengthOf50()
{
    Assert.Greater(_resultsData.Rows.Count, 0);

    foreach (DataRow row in _resultsData.Rows)
        Assert.AreEqual(5, (decimal)row[Column.Name]);
}

But for me, creating reports of the text descriptions wasn't the main point. The main point was just to better organize the test. When I started writing the code, I thought maybe I'd just skip the text, but then I thought, "Oh well, I might as well do it."

And that was fine, but when the requirements changed, I had trouble keeping the text synchronized with the code.

After I wrote the initial code, the team decided that instead of having strength on a scale from 0-60, we'd have it on a scale from 0.0-6.0. And when I changed the code, I was in a hurry, since that change hadn't been part of the original estimate, and consequently threatened our timeline.

So I ended up with text that said 50 and code that said 5.

4 comments:

  1. Why dont you just execute the text instead? See http://nbehave.codeplex.com/Wiki/View.aspx?title=With%20textfiles%20and%20ActionSteps&referringTitle=Home

    ReplyDelete
  2. If you made your test take parameters from the text. You would correct the numbers in your text. Your functions can take parameters. I'v writen my storyes like this

    story.AsA("Bowler")
    .IWant("The score of my game to be calculated")
    .SoThat("I can know my total score");

    story.WithScenario("Gutter game")
    .Given("a game of bowling", () => g = new Game())
    .When("All my $rolls rolls are $pins", 20, 0, RollMany)
    .Then("My score should be $score", 0, (expectedScore) =>
    g.Score().ShouldEqual(expectedScore));


    Now for the second story i can write.
    story.WithScenario("All once")
    .Given("a game of bowling")
    .When("All my 20 rolls are 1")
    .Then("My score should be 20");

    Since i have declared the sentence template it will run the RollManny function with parameters
    20 rols and 1 pin in each roll.

    ReplyDelete
  3. @Morgan
    That's awesome. I had no idea NBehave could do that.

    @Arnt
    And I didn't know you could do that either. Good stuff.

    Yep, I have a bit to learn about NBehave.

    ReplyDelete
  4. There are some new ways currently available on the NBehave trunk to write tests (will be included in v0.5). I've posted about them at http://www.sharpfellows.com/post/NBehave-Different-ways-to-drive-BDD-tests-specs.aspx.

    ReplyDelete