-
Notifications
You must be signed in to change notification settings - Fork 20
Fluent interfaces
While NBehave’s main focus is on executable scenarios, it also provides facilities to enable BDD within the context of another unit test framework. These facilities are largely just syntactic sugar on the one hand (because BDD is in one sense just a rebranding of TDD), but they enable some important benefits:
seperation of test steps and their implementation reuse of test steps parameterised test steps BDD language in tests Hopefully some of these potential benefits will become clear in the examples below.
The first thing to do is to create a test project, using your favourite framework. Currently NUnit, MbUnit, XUnit and MSTest are supported by NBehave.
You then need to install one of the following nuget packages:
Install-Package nbehave.spec.nunit
Install-Package nbehave.spec.mbunit
Install-Package nbehave.spec.xunit
Install-Package nbehave.spec.mstest
Create a test class that derives from NBehave.Fluent.Framework.<testframework>.ScenarioDrivenSpecBase
. This class
The base class defines an abstract method, CreateFeature. You need to implement this to return a Story object. Your method can simply return a new Story() if you like, but you add context to the test class if you fill in some detail on this.
You can now start writing tests as you normally would do, although your test methods can now use one of the styles listed below.
In this style, we supply the step implementations as we define the scenario. The example below uses one-line lambdas, but block lambdas or method delegates can also be used.
[TestFixture]
public class CalculatorSpecWithImplementationInHelperClass : ScenarioDrivenSpecBase
{
protected override Feature CreateFeature()
{
return new Feature("addition of two numbers")
.AddStory()
.AsA("user")
.IWant("my calculator to add number together")
.SoThat("I don't need to try and do it in my head");
}
[Test]
public void should_add_1_plus_1_correctly()
{
Feature.AddScenario()
.Given("a calculator", () => calculator = new Calculator())
.And("I have entered 1 into the calculator", () => calculator.Enter(1))
.And("I have entered 1 into the calculator", () => calculator.Enter(1))
.When("I add the numbers", () => calculator.Add())
.Then("the sum should be 2", () => calculator.Value().ShouldEqual(2));
}
}
This style of writing BDD tests interleaves the actual scenario with the step implementations. The step text does help to clarify the intent of the test, but it is not separated very cleanly from the step implementations.
This style of test locates step implementations by relying on method naming convention. The test needs to specify a “helper object” which actually contains the methods. In this example, we have located the methods in the base class and so use “this” as the helper object, but they could equally be defined on a completely separate object.
[TestFixture]
public class CalculatorSpecWithImplementationInBaseClass : CalculatorAdditionSpec
{
protected override Feature CreateFeature()
{
return new Feature("addition of two numbers")
.AddStory()
.AsA("user")
.IWant("my calculator to add number together")
.SoThat("I don't need to try and do it in my head");
}
[Test]
public void should_add_1_plus_1_correctly()
{
var stepHelper = this;
Feature.AddScenario()
.WithHelperObject(stepHelper)
.Given("a calculator")
.And("I have entered 1 into the calculator")
.And("I have entered 1 into the calculator")
.When("I add the numbers")
.Then("the sum should be 2");
}
}
public abstract class CalculatorAdditionSpec : ScenarioDrivenSpecBase
{
private Calculator _calculator;
protected void Given_a_calculator()
{
_calculator = new Calculator();
}
protected void And_I_have_entered_1_into_the_calculator()
{
_calculator.Enter(1);
}
protected void When_I_add_the_numbers()
{
_calculator.Add();
}
protected void Then_the_sum_should_be_2()
{
_calculator.Value().ShouldEqual(2);
}
}
There is a strong separation here between the scenario definition and the step implementations. This style of test enable reuse of steps – you typically find that you start building up a library of reusable test steps and so new tests can require less effort.
This style of test uses methods tagged with NBehave attributes to locate test steps. These are the same attributes that drive the executable scenarios, and so we get some benefits from them. The “helper object” which hosts the methods again needs to be passed into NBehave and in this example we reference it by type and let NBehave construct the instance.
[TestFixture]
public class CalculatorSpecWithImplementationInHelperClass : ScenarioDrivenSpecBase
{
protected override Feature CreateFeature()
{
return new Feature("addition of two numbers")
.AddStory()
.AsA("user")
.IWant("my calculator to add number together")
.SoThat("I don't need to try and do it in my head");
}
[Test]
public void should_add_1_plus_1_correctly()
{
Feature.AddScenario()
.WithHelperObject<AddNumbers>()
.Given("a calculator")
.And("I have entered 1 into the calculator")
.And("I have entered 1 into the calculator")
.When("I add the numbers")
.Then("the sum should be 2");
}
}
[ActionSteps]
public class AddNumbers
{
private Calculator _calculator;
[BeforeScenario]
public void SetUp_scenario()
{
_calculator = new Calculator();
}
[Given(@"I have entered $number into the calculator")]
public void Enter_number(int number)
{
_calculator.Enter(number);
}
[When(@"I add the numbers")]
public void Add()
{
_calculator.Add();
}
[Then(@"the sum should be $result")]
public void Result(int result)
{
_calculator.Value().ShouldEqual(result);
}
}
As you can see, this style of test also has a strong separation between the scenario definition and the step implementations and also promotes reuse. The advantage of this style, is that we can use the powerful text parsing built into NBehave to extract parameter values from the scenario definition. These parameterised steps are generally easier to reuse.
The other advantage of this style of test is that the “helper class” here is exactly the same as would be required for an executable scenario. So this style allows a team to migrate to using executable scenarios, or even to mix and match how their tests are implemented.