Partager via


Trying out Behave#

One of the neat things I saw at the Agile conference was a short demo of RSpec and RBehave. Intrigued, I did a quick search and found the .NET equivalents: Behave# and NSpec.

Note: I'm not sure I completely understand the differences between xSpec and xBehave as they both quote Behaviour-Driven Design as their goal. I think that the Behave libraries are just a way to change the nomenclature of xUnit from 'test' to 'behaviour'. The Spec ones are about writing user stories in a sort of DSL that a product owner or PM could understand.

Since developer stories can be user stories, I thought it would fun to use Behave# to implement the Stack example from Jim Newkirk's Test-Driven Development in .NET book. Here's what I ended up with (apologies for the formatting - it's much easier to read inside VS):

Stack stack = null;
bool isEmpty = false;
object tempObject = null, tempObject2 = null, tempObject3 = null;
List<int> knownObjects = new List<int>();
knownObjects.Add(1);
knownObjects.Add(2);
knownObjects.Add(3);

Story stackStory = new Story("Basic stack operations");

stackStory
.AsA("developer")
.IWant("to peform operations with a stack")
.SoThat("I can save my data");

stackStory
.WithScenario("A brand new stack")

.Given("a stack I created", delegate { stack = new Stack(); })
.When("I query its state", delegate { isEmpty=stack.IsEmpty; })
.Then("it should be empty", delegate { Assert.True(isEmpty); })

.Given("a stack I created")
.When("I push a single object",

          delegate { stack.Push(new object()); })
.And("I query its state")
.Then("it should not be empty",

          delegate { Assert.False(isEmpty); })

.Given("a stack I created")
.When("I push a single object")
.And("pop the object", delegate {tempObject=stack.Pop(); })
.And("I query its state")
.Then("it should be empty")

.Given("a stack I created")
.When("I push a known object",

          delegate { stack.Push(knownObjects[0]); })
.And("pop the object")
.Then("the two objects are equal",

          delegate {

               Assert.Equal<object>

                    (knownObjects[0], tempObject); })

.Given("a stack I created")
.When("I push three known objects",

          delegate{knownObjects.ForEach(

          delegate(int val){stack.Push(val);});})
.And("pop each one ",

          delegate { tempObject = stack.Pop();

                     tempObject2 = stack.Pop();

                     tempObject3 = stack.Pop(); })
.Then("all three objects are the same",
         delegate
         {
           Assert.Equal<object>(knownObjects[2], tempObject);
           Assert.Equal<object>(knownObjects[1], tempObject2);
           Assert.Equal<object>(knownObjects[0], tempObject3);
})

.Given("a stack I created")
.When("I pop an object it throws an exception",

         typeof(InvalidOperationException),

         delegate(Type type)

              { Assert.Throws(type, delegate { stack.Pop(); }); })
.Then("", delegate { ;})

.Given("a stack I created")
.When("I push a single object")
.And("call top", delegate { tempObject = stack.Top(); })
.And("I query its state")
.Then("it should not be empty")

.Given("a stack I created")
.When("I push a known object")
.And("call top")
.Then("the two objects are equal")

.Given("a stack I created")
.When("I push three known objects")
.And("call top")
.Then("the last item is equal to the top one",

         delegate

         { Assert.Equal<object>(knownObjects[2], tempObject); })

.Given("a stack I created")
.When("I push a known object")
.And("call top repeatedly",

         delegate

         {

            tempObject = stack.Top();

            tempObject2 = stack.Top();

            tempObject3 = stack.Top();

         })
.Then("all objects are equal to what was pushed",
         delegate
         {
           Assert.Equal<object>(knownObjects[0], tempObject);
           Assert.Equal<object>(knownObjects[0], tempObject2);
           Assert.Equal<object>(knownObjects[0], tempObject3);
})

.Given("a stack I created")
.When("I call top it throws an exception",

         typeof(InvalidOperationException),

         delegate(Type type)

           { Assert.Throws(type, delegate { stack.Top(); }); })
.Then("")

.Given("a stack I created")
.When("I push a null object", delegate { stack.Push(null); })
.And("I query its state")
.Then("it should not be empty")

.Given("a stack I created")
.When("I push a null object")
.And("pop the object")
.Then("the returned object is null",

          delegate { Assert.Null(tempObject); })

.Given("a stack I created")
.When("I push a null object")
.And("call top")
.Then("the returned object is null");

}

and when I run the tests, this is the output:

 

Story: Basic stack operations

Narrative:
    As a developer
    I want to peform operations with a stack
    So that I can save my data

    Scenario 1: A brand new stack
        Given a stack I created
        When I query its state
        Then it should be empty

        Given a stack I created
        When I push a single object
            And I query its state
        Then it should not be empty

        Given a stack I created
        When I push a single object
            And pop the object
            And I query its state
        Then it should be empty

        Given a stack I created
        When I push a known object
            And pop the object
        Then the two objects are equal

        Given a stack I created
        When I push three known objects
            And pop each one
        Then all three objects are the same

        Given a stack I created
        When I pop an object it throws an exception: System.InvalidOperationException
        Then

        Given a stack I created
        When I push a single object
            And call top
            And I query its state
        Then it should not be empty

        Given a stack I created
        When I push a known object
            And call top
        Then the two objects are equal

        Given a stack I created
        When I push three known objects
            And call top
        Then the last item is equal to the top one

        Given a stack I created
        When I push a known object
            And call top repeatedly
        Then all objects are equal to what was pushed

        Given a stack I created
        When I call top it throws an exception: System.InvalidOperationException
        Then

        Given a stack I created
        When I push a null object
            And I query its state
        Then it should not be empty

        Given a stack I created
        When I push a null object
            And pop the object
        Then the returned object is null

        Given a stack I created
        When I push a null object
            And call top
        Then the returned object is null

1 passed, 0 failed, 0 skipped, took 1.15 seconds.

So is it useful? Like most things, there are situations where it's appropriate and others where it's not. I'm starting with a contrived example to begin with, and so not demonstrating its full usefulness. Regardless, there are a few points I'd like to make after playing with it for a while:

  • It's nice to repeat snippets of an English sentence instead of code, although I suspect the novelty would fade after a while :)
  • If you want to specify Exceptions as part of your story, this forces you to move away from [ExpectedException]. Which is a Good Thing anyway. I'm using an Assert replacement from Codeplex to accomplish this.
  • I'm a big fan of having very descriptive method names for unit tests - this is taking descriptive to the next level :)

On the (possibly) less good side of things:

  • If a story fails, then the rest of the test isn't executed. It doesn't make sense to only put one scenario per test either, because then you'll end up repeating the delegate code.

  • If a story fails, it's sometimes really awkward to figure out why. It's really annoying when you have to debug into the delegate.

  • The test/stories are dependent on each other. This is a very disconcerting concept coming from an EDD background.

  • It's very difficult to test loops, or perhaps I just haven't found a good way. If you look at the place where Top() is called repeatedly (fifth last story) and we check to make sure it always returns the same value, I had to use named objects to accomplish this. The original test looked like this:

    [Test]public void PushTopNoStackStateChange(){    string pushed = "44";    stack.Push(pushed);    for(int index = 0; index < 10; index++)    {        string topped = (string)stack.Top();        Assert.AreEqual(pushed, topped);    }}

    and I would claim that's easier to read. There's some flexibility lost there.

I'd like to see some more real world examples of this being used; this could be a powerful tool in the right circumstances. And of course as the library is enhanced, some of the things I've talked about may become more feasable.

Comments

  • Anonymous
    August 30, 2007
    Just had a look at your bheave# example and read a bit more about it. I agree with one of your point that if you want to specify Exceptions as part of your story, this forces you to move away from [ExpectedException]. Which is a Good Thing anyway.It is quite verbose thoughI think i am going to try a real world example and see how it goes or to put it more appropriately to see if it grows on me and how comfortable i feel.
  • Anonymous
    August 30, 2007
    I agree with Brad and Jim that we should be moving away from [ExpectedException] regardless of what framework we're working in :)The verbose-ness (I think) is one of the qualities that could make it attractive to product owners or PMs. The code is really to express acceptance tests rather than developer-focused 'unit' tests.Please write back whenever you get a chance to try this in the real world .. I would love to see more examples :)
  • Anonymous
    August 30, 2007
    I wonder, and I'm allowed to be wrong on this, if it wouldn't be more effective to A) improve our programming languages (and usage of) for readability and B) require PM's to learn these languages. "But a PM would never learn...." it seems a lot easier (and cheaper) than forcing developers to learn and maintain this overly verbose artifact.
  • Anonymous
    August 30, 2007
    A) YesB) YesI guess in the meantime, it's interesting to see what we can accomplish with the tools we have :)Re the 'developers learning/maintaining stuff' .. you can bet that there is already work going on to do the translation from spoken language wiki documents (à la Fitnesse) to what I'm demonstrating here and more.
  • Anonymous
    September 02, 2007
    Esplorando il Behaviour Driven Development
  • Anonymous
    September 03, 2007
    The comment has been removed
  • Anonymous
    September 03, 2007
    Hi Joe,Thanks for the comments :) I'll definitely let you know how it works in the real world. It would also be really great to get some different (and more complex) examples than the Account one.
  • Anonymous
    September 04, 2007
    I couldn't agree with you more! I am getting tired of using the accounting example.  I was thinking of something like a role playing game but that doesn't really apply to a majority of the vertical markets out there.  May go with the traditional Video Store example.Nelson Montalvo has a 5 part series on using Behave#; he is using it to build the application he is working on.http://codemonkey.nmonta.com/2007/08/14/behave-part-5/JO