Click here to Skip to main content
15,892,674 members
Articles / Programming Languages / C#

Behavior-Driven Development with NBehave

Rate me:
Please Sign up or sign in to vote.
4.97/5 (35 votes)
13 Jan 2009CPOL11 min read 88.9K   376   81  
A BDD tutorial using NBehave and MbUnit.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
  <title></title>
</head>
<body>




<h2>Contents</h2><ul>
  <li><a href="#Introduction">Introduction</a></li>  
  <li><a href="#WhatrsquosBDD">What&rsquo;s BDD?</a></li>  
  <li><a href="#ToolsoftheTrade">Tools of the Trade</a></li>  
  <li><a href="#ExampleBankAccount">Example: Bank Account</a></li>  
  <li><a href="#Tests">Tests</a></li>  
  <ul>  
    <li><a href="#Overview">Overview</a></li>    
    <li><a href="#TheTestClass">The Test Class</a></li>    
    <li><a href="#Stories">Stories</a></li>    
    <li><a href="#Scenarios">Scenarios</a></li>    
  </ul>  
  <li><a href="#Conclusion">Conclusion</a></li>  
</ul>
<h2><a name="Introduction"></a>Introduction</h2>
<p>This article is a pithy introduction to the concept of Behavior-Driven Development. In it, I present the way that the C# model of a bank account, with its trademark <code>Deposit()</code> and <code>Withdraw()</code> methods, can be created using BDD techniques. To do this, I will use the NBehave framework for BDD, and MbUnit as the underlying test framework.</p>
<p>Please note that for this introductory article, I&rsquo;m avoiding the issue of mock objects &ndash; something that shouldn&rsquo;t be avoided in real projects, of course. Also, I will not talk about the issue of code coverage which is, of course, also important if you want to get your tests to cover all possible scenarios.</p>
<h2><a name="WhatrsquosBDD"></a>What&rsquo;s BDD?</h2>
<p>Okay, you know what TDD is, right? TDD, also known as Test-Driven Development, is a very simple concept. In TDD, you write a unit test for some not-yet-implemented functionality, see it fail, add the necessary functionality, then see it succeed. Using TDD, your unit test for a withdrawal from a bank accout might look like this:</p>
<pre>
[Test]
public void WithdrawalTest()
{
  Account a = new Account(100);
  a.Withdraw(30);
  Assert.AreEqual(a.Balance, 70);
  Assert.Throws&lt;InsufficientFundsException&gt;(a.Withdraw(100));
}
</pre>
<p>This unit test is fine, but it tells you very little about what you are actually testing. What&rsquo;s essentially happening above is a comparison of states. For example, after the first withdrawal, you&rsquo;re comparing the <code>Balance</code> state of the account with the value 70. There&rsquo;s no notion of &ldquo;the balance being reduced by the amount withdrawn&rdquo; here. This test is very mechanical, and is not particularly descriptive.</p>
<p>Enter the notion of BDD. Basically, BDD is designed around the idea that, instead of describing the code under test as some sort of final state machine, you actully give it qualities related to its behavior. What this means is that you describe, in English, what each step in the unit test does, and associate the particular code with this step. You also provide additional metadata about the user <em>story</em>, i.e., what the particular user would want to happen in this test, and why it&rsquo;s important for them.</p>
<p>Sounds confusing? We&rsquo;ll take a look at a practical example in a moment, but before that, let&rsquo;s briefly discuss the libraries you need to add BDD to your application.</p>
<h2><a name="ToolsoftheTrade"></a>Tools of the Trade</h2>
<p>First of all, you need NBehave, the library that actually allows BDD in the first place. Although the NBehave homepage is <a href="http://nbehave.org/">here</a>, you should get the latest version from their <a href="http://code.google.com/p/nbehave/">Google Code</a> repository. This is <em>essential</em>, because only version 0.4 contains the support for MbUnit that we need. If you get an earlier version, you won&rsquo;t be able to get MbUnit and NBehave to play together.</p>
<p>NBehave runs on top of a &lsquo;conventional&rsquo; unit testing framework, and I&rsquo;m going to use MbUnit for this article. You can get MbUnit <a href="http://www.gallio.org">here</a>, as part of the Gallio automation framework. The download link is right on the front page, so I&rsquo;d just go for that.</p>
<p>As with conventional unit testing, you need some sort of test runner to actually run the tests and report results. If you have ReSharper, you&rsquo;ve got nothing to worry about, since Gallio comes with a ReSharper plug-in for running all sorts of units tests, MbUnit included. If you haven&rsquo;t got ReSharper, you can use Gallio itself as it comes with its own test runner. All you have to do is open the assembly containing your unit tests, and you&rsquo;re set.</p>
<h2><a name="ExampleBankAccount"></a>Example: Bank Account</h2>
<p>Let&rsquo;s define an entity that models a bank account. It will have the following features:</p>
<ul>
  <li>A <code>Balance</code> property.</li>
  <li>A <code>Withdraw(int amount)</code> method for withdrawing money from the account. If the amount of money is insufficient, we&rsquo;ll throw an <code>InsufficientFundsException</code>.</li>
  <li>A <code>Deposit(int amount)</code> method for depositing money to the account.</li>
  <li>A static <code>Transfer(Account from, Account to, int amount)</code> method for transfering money from one account to another.</li>
</ul>
<p>For the above methods, let us also agree to throw an <code>ArgumentException</code> if the <code>amount</code> passed to any function is non-positive.</p>
<p>This very simple model is enough for us to get started with. In fact, I can code the interface for this class right now, since we need it anyway (without an interface, we cannot write tests &ndash; even failing ones).</p>
<pre>
public sealed class Account
{
  private int balance;

  public int Balance
  {
    get { return balance; }
    set { balance = value; }
  }

  public void Deposit(int amount)
  {
  }

  public void Withdraw(int amount)
  {
  }

  public static void Transfer(Account from, Account to, int amount)
  {
  }
}
</pre>
<p>For the sake of completeness, here is the <code>InsufficientFundsException</code> class:</p>
<pre>
public class InsufficientFundsException : Exception
{
  public InsufficientFundsException(int requested, int available)
  {
    AmountAvailable = available;
    AmountRequested = requested;
  }

  public int AmountRequested { get; set; }

  public int AmountAvailable { get; set; }
}
</pre>
<p>With the model in place, we can finally get started with BDD! Hooray!</p>
<h2><a name="Tests"></a>Tests</h2>
<h3><a name="Overview"></a>Overview</h3>
<p>Let&rsquo;s get started by adding the refences to the project:</p>
<p>First, add a reference to the <code>MbUnit</code> assembly. It&rsquo;s in the GAC, so no searching is required. We only need the <code>MbUnit.Framework</code> namespace from this assembly for some essential attributes, such as <code>[SetUp]</code> &ndash; everything else is handled directly by NBehave.</p>
<p>We need three assemblies from NBehave &ndash; these are <code>NBehave.Narrator.Framework</code>, <code>NBehave.Spec.Framework</code> and <code>NBehave.Spec.MbUnit</code>. The first of this assemblies imports the API for the so-called Narrator &ndash; an interface which mimics a narrator telling the user story. Basically, you&rsquo;ll be using the code to say things like &ldquo;as a user I want account withdrawal to work properly&rdquo;. The second framework contains the base definition of a <code>SpecBase</code> class &ndash; this class is important because our test classes will need to derive from it. Finally, the third framework is &ndash; you&rsquo;ve guessed it &ndash; a bridge between MbUnit and NBehave. In actual fact, this framework <em>relies on</em> the <code>NBehave.Spec.Framework</code>, in that it contains a specialization of <code>SpecBase</code> for MbUnit.</p>
<p>
One thing to note about <code>NBehave.Spec.MbUnit</code> is that, in addition to the <code>SpecBase</code> class, which we simply subclass and forget about (for the most part), this assembly also contains extension methods that mirror, to some extent, the <code>Assert</code> functionality of MbUnit. Here&rsquo;s what I mean:
</p>
<pre>
// in MbUnit, we write this
Assert.AreEqual(a.Balance, 70);
// but in NBehave, we write this
a.Balance.ShouldEqual(70);
</pre>
<p>
The above statements are equivalent, but NBehave&rsquo;s version is perhaps more clear in expressing what it actually means. Please note, however, that at the moment, the extension methods which enable this behavior <em>do not</em>, at the time of this writing, cover every single functionality of MbUnit&rsquo;s <code>Assert</code> class. Thus, however nice this syntax is, you won&rsquo;t always be able to use it &ndash; particularly if you rely on the more advanced functionality of MbUnit.</p>
<h3><a name="TheTestClass"></a>The Test Class</h3>
<p>
To cut the story short, here is the skeleton of my test class:
</p>
<pre>
[
  Author("Dmitri", "dmitrinesteruk@gmail.com"),
  Wrote("Account operation tests"),
  TestsOn(typeof(Account)),
  For("Banking system")
]
public class AccountTest : SpecBase
{
  public Account account;
  public Account account2;

  [SetUp]
  public void Initialize_before_each_test()
  {
    account = new Account();
    account2 = new Account { Balance = 100 };
  }
}
</pre>
<p>
There isn&rsquo;t anything interesting happening here. Our test class derives from <code>SpecBase</code> and is decorated with fairly standard MbUnit test attributes that have been redefined (see next section) to make them more readable. In the test class itself, I create two accounts &ndash; one empty, and one with $100 on it. You&rsquo;ll notice that although I&rsquo;m using the old-fashioned <code>[SetUp]</code> attribute, the method name is a bit strange. In fact, it&rsquo;s one of TDD conventions to have method names that actually describe what&rsquo;s going on, in English rather than some shorthand notation. So that&rsquo;s precisely what&rsquo;s being done here.
</p>
<h3><a name="Stories"></a>Stories</h3>
<p>
It all begins with a story. Once upon a time, NBehave developers wrote the (somewhat clever) class they called <code>Story</code>. This class was designed to describe a particular set of usage scenarios which you are trying to test. For example, the story would describe the Deposit action on the account as follows:
</p>
<pre>
[Story, That, Should("Increase account balance when money is deposited")]
public void Deposit_should_increase_account_balance()
{
  Story story = new Story("Deposit");
  story.AsA("User")
    .IWant("The bank account balance to increase by the amount deposited")
    .SoThat("I can deposit money");

  // scenarios here
}
</pre>
<p>
What&rsquo;s going on here, then? First of all, the attributes that decorate the test method are really using the familiar xUnit testing attributes (e.g., <code>[Test]</code>), but for BDD, many people (myself included) redefine them using C#&lsquo;s <code>using</code> syntax:
</p>
<pre>
using That = MbUnit.Framework.TestAttribute;
using Describe = MbUnit.Framework.CategoryAttribute;
using For = MbUnit.Framework.CategoryAttribute;
using Wrote = MbUnit.Framework.DescriptionAttribute;
using Should = MbUnit.Framework.DescriptionAttribute;
</pre>
<p>
The only reason for these redifinitions is to make the tests more readable. This trend permeates NBehave &ndash; get used to it. Now, let&rsquo;s take a look at what we did with the <code>Story</code> class. There is no unit test here! All we&rsquo;re doing is describing a user story, using English and NBehave&rsquo;s fluent interface. As you&rsquo;ll see later, most of NBehave uses a fluent interface, i.e., an interface where each function returns <code>this</code>, allowing long chains of calls to be made.
</p>
<p>
Just to be completely clear, we wrote the above story definition in order to reflect a particular requirement. For example, the requirements state that a user deposits money and their bank account grows accordingly, so we wrote just that. This story definition will appear in the output of our unit tests, making it somewhat easier to identify what it is we are testing.
</p>
<h3><a name="Scenarios"></a>Scenarios</h3>
<p>
We&rsquo;ve created a story definition, so let&rsquo;s write a unit test and see it fail. In order to test it, we have to provide something called a <em>scenario</em> &ndash; a description of one possible thing that can happen. For example, you might withdraw from an empty bank account, or one that doesn&rsquo;t have enough money. Or you might withdraw an amount you actually have. Or you might try to withdraw a negative amount. All these cases are scenarios, and to get 100% coverage, you would need to test each one. However, let us start with something simple:
</p>
<pre>
story.WithScenario("Money deposit")
  .Given("My bank account is empty", () =&gt; { account.Balance = 0; })
  .When("I deposit 100 units", () =&gt; account.Deposit(100))
  .Then("The account balance should be 100", () =&gt; account.Balance.ShouldEqual(100));
</pre>
<p>
All right, so this snippet probably exposes 99% of what BDD is about. Essentially, we&rsquo;re defining a scenario (a money deposit) and then describe the preconditions, the test itself, and the postconditions &ndash; all in one C# statement! Here&rsquo;s what happens in our code:</p>
<ul>
<li>We start with a <code>WithScenario()</code> call which tells the system what scenario this is. You&rsquo;ve guessed it &ndash; the information does get output to the test tool! So does everything that follows it, mind you.</li>
<li>Then, we use the <code>Given()</code> method to define a precondition &ndash; i.e., an initially empty bank account. This function &ndash; just like the next two &ndash; is used with two parameters here: a string describing what&rsquo;s going on, plus an <code>Action</code> parameter which <em>does what the previous parameter describes</em>.</li>
<li>The <code>When()</code> is used to describe the action whose consequences we&rsquo;re trying to test.</li>
<li>Finally, the <code>Then()</code> call is where we check that the correct thing happened.</li>
</ul>
<p>You have probably noticed by now that there&rsquo;s a fair bit of lambda syntax in the test. This is because the parameters in each of the clauses are of <code>Action</code> variety, so using a lambda syntax is somewhat more concise than using the <code>delegate</code> keyword.</p>
<p>Since we&rsquo;re doing BDD here, let&rsquo;s run the test to see it fail. On my system, I get the following error message:</p>
<pre>
*** DebugTrace ***
Story: Deposit

Narrative:
    As a User
    I want The bank account balance to increase by the amount deposited
    So that I can deposit money

    Scenario 1: Money deposit
        Given My bank account is empty
        When I deposit 100 units
        Then The account balance should be 100 - FAILED


MbUnit.Core.Exceptions.NotEqualAssertionException:
  Equal assertion failed: [[0]]!=[[100]]
</pre>
<p>
Can you see how the test runner took the specification we wrote and actually output it as a readable scenario? It also shows us the point where it fails, so we don&rsquo;t have to decypher cryptic MbUnit messages (they are still available, so if you feel like it, be my guest). So now that we have a failing test, let&rsquo;s add the missing functionality and try again:
</p>
<pre>
public void Deposit(int amount)
{
  balance += amount;
}
</pre>
<p>
Simple enough. Now, when we run the test, it succeeds. That&rsquo;s all there is to TDD/BDD, really! But let&rsquo;s look at a more complex scenario &ndash; depositing a negative amount. We should get an exception, and our bank account balance should remain unchanged. Here&rsquo;s how such a test would look:
</p>
<pre>
story.WithScenario("Negative amount deposit")
  .Given("My bank account is empty", () =&gt; { account.Balance = 0; })
  .When("I try to deposit a negative amount", () =&gt; { })
  .Then("I get an exception",
        () =&gt; typeof(Exception).ShouldBeThrownBy(() =&gt; account.Deposit(-100)))
  .And("My bank account balance is unchanged",
       () =&gt; account.Balance.ShouldEqual(0));
</pre>
<p>There are two things to note here. First, we use the <code>ShouldBeThrownBy()</code> extension method to ensure that when calling <code>Deposit()</code> with a negative amount we do, in fact, get an exception. Also, we use the <code>And()</code> method to make sure that, in addition to the exception being called, the account balance remains unchanged. Running the test on our code, we get the following output:</p>
<pre>
*** DebugTrace ***
Story: Deposit

Narrative:
    As a User
    I want The bank account balance to increase by the amount deposited
    So that I can deposit money

    Scenario 1: Money deposit
        Given My bank account is empty
        When I deposit 100 units
        Then The account balance should be 100

    Scenario 2: Negative amount deposit
        Given My bank account is empty
        When I try to deposit a negative amount
        Then I get an exception - FAILED
</pre>
<p>
The output is more or less expected, but we&rsquo;ve had to break the paradigm somewhat in order to get the output we have here. Let me explain. First of all, I failed to provide an <code>Action</code> for when I was expected to actually deposit the amount. Instead, I used an empty lambda:
</p>
<pre>
  .When("I try to deposit a negative amount", () =&gt; { })
</pre>
<p>Basically, I cannot attempt a negative deposit in this clause, because I also intend to catch the exception and check that it is of the type I&rsquo;m expecting &ndash; something more suitable to a <code>Then()</code> clause. On the other hand, I don&rsquo;t want to leave the <code>When()</code> clause actionless because if I do, the textual output (i.e. the &ldquo;I try&hellip;&rdquo; first parameter) will <em>not</em> appear in the output. This may be a bug or a feature, but in any case I use an empty lambda to make sure that doesn&rsquo;t happen.</p>
<p>At this stage, I can simply finish off my naive implementation of the <code>Deposit()</code> function and run the test again. It would look something like this:</p>
<pre>
public void Deposit(int amount)
{
  if (amount &lt;= 0)
    throw new Exception();
  balance += amount;
}
</pre>
<p>
Here&rsquo;s a situation where you cannot use the nice extension methods. Suppose you are testing a transfer between one bank account and another. You want to make sure that, if the transfer is possible (i.e., there is sufficient amount of money, it&rsquo;s a non-negative amount, etc.) that no exception should be thown when the transfer happens. Since there is no <code>ShouldNotThrow()</code> extension method, we end up writing the following:
</p>
<pre>
story.WithScenario("Valid transfer")
  .Given("I have 100 dollars", () =&gt; { account.Balance = 100; })
  .And("You have 100 dollars", () =&gt; { account2.Balance = 100; })
  .When("I give you 50 dollars",
        () =&gt; Assert.DoesNotThrow(() =&gt; Account.Transfer(account, account2, 50)))
  .Then("I have 50 dollars left", () =&gt; account.Balance.ShouldEqual(50))
  .And("You have 150 dollars", () =&gt; account2.Balance.ShouldEqual(150));
</pre>
<h2><a name="Conclusion"></a>Conclusion</h2>
<p>
By now you&rsquo;ve probably figured out that, apart from the way things are described (fluent interfaces, English descriptions), there is <em>nothing new</em> in NBehave. In fact, some people are annoyed that BDD is essentially a kind of &rsquo;verbose xUnit&rsquo; that does the same things, but insists on describing everything you do. However, the benefit you get from this <em>traceability</em>. For example, the words in your unit tests can refer to use cases in your requirements specification, thus making it easier to show that your product conforms to a particular spec. In fact, it&rsquo;s probably feasible to write a transformation tool that takes an English sentence and turns it into a skeleton NBehave scenario. If you write such a tool, please let me know!
</p>
<p>This is it for this article. Thanks for reading. Comments and suggestions are much appreciated!</p>









</body>
</html>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and C++, though I'm open to suggestions.

Comments and Discussions