SpecFlow is a BDD (Behaviour Driven Development) tool that supplies templates and integration for Visual Studio.
If you are new to BDD (or TDD or unit testing in general) go off to your favourite search engine and search. I’m sure there are far better definitions out there than I’ll probably come up with or try here. For those of you not wishing to waste a moment let’s start looking at SpecFlow.
The idea behind a feature is to define a test scenario in a more human readable way, in fact the perfect situation would be for the domain expert to write the scenarios to act as acceptance tests. SpecFlow is based upon Cucumber and uses the DSL Gherkin to allow the domain experts to write the scenarios.
Enough waffle let’s write a feature. We’ve got a Trade object which has a TradeDate and a StartDate. The StartDate should be calculated to be the TradeDate + 3 days.
Feature: Check the trade date is defaulted correctly Scenario: Default the trade date Given Today's date is 23/10/2012 When I create a trade Then the resultant start date should be 26/10/2012
The Feature text is just a name for the feature and can be followed by free form text to add documentation, in the above case I’ve not bothered. Instead I’ve gone straight to creating a Scenario.
A Scenario is basically the definition of what we want to test. Using the keywords Given, When, Then, And and But (note: I’ve not checked this is the full list). We can create the scenario for the test (don’t get too excited we still need to write the test) and then the Visual Studio integration will by default run the SpecFlowSingleFileGenerator custom tool against this feature file and create a feature.cs which contains the code for calling into your tests.
So we now have a feature file, potentially supplied by our domain expert. From this we get a .cs file generated which calls our code in the correct order and potentially passing arguments from the scenario to our test code. In the above case we want the dates to be passed to our tests. So now to write the code which will be run from the scenario generated code and here we add NUnit (or your preferred unit testing lib.) to check things are as expected.
[Binding] public class Default_the_trade_date { private Trade trade; private DateTime todaysDate; [Given("Today's date is (.*)")] public void Given_Todays_Date_Is(DateTime today) { todaysDate = today; } [When("I create a trade")] public void When_I_Create_A_CreateTrade() { trade = new Trade {TradeDate = todaysDate}; } [Then("the resultant start date should be (.*)")] public void Then_Start_Date_Should_Be(DateTime dateTime) { Assert.AreEqual(trade.StartDate, dateTime); } }
Note: I’ve named this class Default_the_trade_date this is the name I gave the scenario, this is not a requirement but makes it obvious what this code is for.
In the above you’ll notice the use of attributes which match the text in the scenario for each step, i.e. Given, When, Then. The text uses regular expressions, so matches the text to the scenario text but in this case the (.*) converts the dates into arguments to be passed into the test methods.
You can now right mouse click on your project or better still your .feature and select Run SpecFlow Scenarios. Unfortunately the above code will fail. As I’m using British date formats, i.e. dd/MM/yyyy and the default DateTime parser is en-US. So we need to edit the App.config for our scenarios to have
<language feature="en-GB" tool="{not-specified}" />
within the specFlow element.
If all went to plan, when you run the specflow scenarios the tests show green.
Note: If we have multiple features and/or scenarios which maybe use the same text, for example in the above code maybe we have several features and scenarios which use the Given “Today’s date is “. We can set the Scope attribute on the test classes and either mark the scenario with a tag
@tradeDate Scenario: Default the trade date ...
or we can use the Feature and/or Scenario name such as
[Binding] [Scope(Feature = "Check the trade date is defaulted correctly")] public class Default_the_trade_date { // code }
See Scoped bindings for more on this and note the preference is towards not using such couplings.