Back to Home

Executable specification: SpecFlow A to Z

specflow · specification by example · bdd · gherkin · test automation · continuous integration · team city

Executable specification: SpecFlow A to Z

  • Tutorial

This article is a continuation of the first part and reveals the technical details of working with the "executable specification" using SpecFlow .

To get started, you need a plug-in for Visual Studio (downloaded from the official site) and the SpecFlow package (installed from nuget ).

So, our Product Owner asked the team to develop a calculator ...

@calculator
Feature: Sum
	As a math idiot
	I want to be told the sum of two numbers
	So that I can avoid silly mistakes
@positive @sprint1
Scenario: Add two numbers
	Given I have entered 50 into the calculator
	And I have entered 70 into the calculator
	When I press add
	Then the result should be 120 on the screen

It is worth paying attention to @ attributes. They have several important properties. First, if you are using NUnit, SpecFlow add the [NUnit.Framework.CategoryAttribute ("calculator")] attribute . It is very convenient for making test plans. The categorization is supported by R #, the native NUnit Runner and Team City.

Let's automate this scenario. By this time, the developers have already prepared the calculator interface:
public interface ICalculator
{
    decimal Sum(params decimal[] values);
    decimal Minus(decimal a, decimal b);
    decimal Sin(decimal a);
    decimal Multiply(params decimal[] values);
    decimal Divide(decimal a, decimal b);
}

Add a service testing context:
public class CalculationContext
{
    private readonly List _values = new List();
    public ICalculator Calculator { get; private set; }
    public decimal Result { get; set; }
    public Exception Exception { get; set; }
    public List Values
    {
        get { return _values; }
    }
    public CalculationContext()
    {
        Calculator = new Calculator();
    }
}

SpecFlow uses special attributes to automate steps:
[Binding]
public class Sum : CalcStepsBase
{
    public CalculationContext Context {get;set;}
    public Sum(CalculationContext context)
    {
        Context = CalculationContext();
    }
    [Given("I have entered (.*) into the calculator")]
    public void Enter(int digit)
    {
        Context.Values.Add(digit);
    }
    [When("I press (.*)")]
    public void Press(string action)
    {
        switch (action.ToLower())
        {
            case "add":
            case "plus":
                Context.Result = Context.Calculator.Sum(Context.Values.ToArray());
                break;
            default: throw new InconclusiveException(string.Format("Action \"{0}\" is not implemented", action));
        }
    }
    [Then("the result should be (.*) on the screen")]
    public void Result(decimal expected)
    {
        Assert.AreEqual(expected, Context.Result);
    }
}

There are several advantages to this approach:
  1. Each step needs to be automated only once.
  2. You avoid problems with complex inheritance chains, the code looks much clearer
  3. Attributes use regular expressions, so a single attribute can “catch” several steps. The When attribute, in this case, will work for the phrases “add” and “plus”
Everything, all steps are implemented, the test can be run directly from the .feature file.

Alternative Record


@positive
Scenario: Paste numbers
	Given I have entered two numbers
	| a | b |
	| 1 | 2 |
	When I press add
	Then the result should be 3 on the screen
[Given("I have entered two numbers")]
public void Paste(Table values)
{
    var calcRow = values.CreateInstance();
    Context.Values.Add(calcRow.A);
    Context.Values.Add(calcRow.B);
}
public class CalcTable
{
    public decimal A { get; set; }
    public decimal B { get; set; }
}

This recording option can be convenient when you need to fill a large object. For example, user account information.

Are we going to test only one data set?

Of course, one test is not enough, so writing dozens of scripts for different numbers is a dubious pleasure. Scenario Outline comes to the rescue
@calculator
Feature: Calculations
	As a math idiot
	I want to be told the calculation result of two numbers
	So that I can avoid silly mistakes
@positive @b12 @tc34
Scenario Outline: Add two numbers
	Given I have entered  into the calculator
	And I have entered  into the calculator
	When I press 
	Then the  should be on the screen
Examples: 
| firstValue | secondValue  | action   | result   |
| 1          | 2            | plus     | 3        |
| 2          | 3            | minus    | -1       |
| 2          | 2            | multiply | 4        |

SpecFlow will substitute the values ​​from the table into the placeholder . Already not bad, but automation needs to be added:
[When("I press (.*)")]
public void Press(string action)
{
    switch (action.ToLower())
    {
        case "add":
        case "plus":
            Context.Result = Context.Calculator.Sum(Context.Values.ToArray());
            break;
        case "minus":
            Context.Result = Context.Calculator.Minus(Context.Values[0], Context.Values[1]);
            break;
        case "multiply":
            Context.Result = Context.Calculator.Multiply(Context.Values.ToArray());
            break;
        case "sin":
            Context.Result = Context.Calculator.Sin(Context.Values[0]);
            break;
        default: throw new InconclusiveException(string.Format("Action \"{0}\" is not implemented", action));
    }
}
[Then("the result should be (.*) on the screen")]
[Then("the (.*) should be on the screen")]
public void Result(decimal expected)
{
    Assert.AreEqual(expected, Context.Result);
}

We changed the second line for better readability. Therefore, the second attribute must be hung on the Result method. At the exit you will receive 3 tests with reports of the form:
Given I have entered 1 into the calculator
-> done: Sum.Enter(1) (0,0s)
And I have entered 2 into the calculator
-> done: Sum.Enter(2) (0,0s)
When I press plus
-> done: Sum.Press("plus") (0,0s)
Then the result should be 3 on the screen
-> done: Sum.Result(3) (0,0s)

What about negative tests?

Add a division check by 0:
@calculator
Feature: Devision
	As a math idiot
	I want to be told the devision of two numbers
	So that I can avoid silly mistakes
@negative @exception
Scenario: Zero division
	Given I have entered 10 into the calculator
	And I have entered 0 into the calculator
	When I press divide
	Then exception must occur

In this case, I do not want to interfere with flies with cutlets and for division I would prefer to have a separate file. The question arises. What to do with context? He stayed in the Sum class . SpecFlow supports injection into the constructor. Select the base class:
public class CalcStepsBase
{
    protected CalculationContext Context;
    public CalcStepsBase(CalculationContext context)
    {
        Context = context;
    }
}

Inherit the new class with steps for dividing from it:
[Binding]
public class Division : CalcStepsBase
{
    public Division(CalculationContext context) : base(context)
    {
    }
    [When("I press divide"), Scope(Scenario = "Zero division")]
    public void ZeroDivision()
    {
        try
        {
            Context.Calculator.Divide(Context.Values[0], Context.Values[1]);
        }
        catch (DivideByZeroException ex)
        {
            Context.Exception = ex;
        }
    }
    [Then("exception must occur")]
    public void Exception()
    {
        Assert.That(Context.Exception, Is.TypeOf());
    }
}

In order that the baids do not conflict, we will divide them into positive and negative
[When("I press (.*)"), Scope(Tag = "positive")]
[When("I press divide"), Scope(Scenario = "Zero division")]

We can filter the scope both by script and by tag.

Data Driven Tests

Four examples of addition, subtraction and multiplication are clearly not enough. There are many systems with impressive volumes of input and output values. In this case, the sheet in the DSL will not look very clear. In order to write tests on the one hand, and on the other, to save the GWT format, you can go in two ways:
  1. Use BDDfy
  2. finish SpecFlow to your needs

I settled on the second option, so as not to support two formats:
[TestFixture]
[Feature(
    "Sum Excel",
    As = "Math idiot",
    IWant = "to be told sum of two numbers",
    SoThat = "I can avoid silly mistakes")]
public class ExcelSumTests : GherkinGenerationTestsBase
{
    [TestCaseSource("Excel")]
    [Scenario("Sum Excel", "excel", "positive", "calculator")]
    public void AddTwoNumbers_TheResultShouldBeOnTheScreen(string firstValue, string secondValue, string action, string result)
    {
        Given(string.Format("I have entered {0} into the calculator", firstValue));
        Given(string.Format("I have entered {0} into the calculator", secondValue), "And ");
        When(string.Format("I press {0}", action));
        Then(string.Format("the result should be {0} on the screen", result));
    }
    public static IEnumerable Excel()
    {
        return ExcelTestCaseDataReader.FromFile("Sum.xlsx").GetArguments();
    }
}

Background and preconditions

Sometimes for a whole bunch of scenarios you need to specify a set of preconditions. Copying them into each scenario is obviously not convenient. Two approaches come to the rescue.

Background

Background: 
	Given Calculator is initialized
@positive
Scenario: Add two numbers
	Given I have entered 1 into the calculator
	When I press sin
	Then the result should be 0.841470984807896 on the screen
In the Background section, you can make more and more preconditions for scripts.

Using tags

Preconditions can also be implemented using tags:
[Binding]
public class Sum : CalcStepsBase
{
    public Sum(CalculationContext context)
        : base(context)
    {
    }
    [BeforeScenario("calculator")]
    [Given("Calculator is initialized")]
    public void InitCalculator()
    {
        Context.Init();
    }
}

The method marked with the BeforeScenario attribute will be executed before the script runs. Attributes are passed to the constructor to limit the scope. We tagged scripts with a calculator tag . Now, before each run of such a script, the InitCalculator method will be executed .

Reports

In order to build a report, you will need the utility specflow.exe and nunit. Below is an msbuild script that runs nunit first and then builds a specflow report.
.C:/Program Files (x86)/NUnit 2.6.2"$(NUnitHome)\bin\nunit-console.exe""$(teamcity_build_checkoutDir)\TestResult.txt""$(teamcity_build_checkoutDir)\TestResult.xml""$(teamcity_build_checkoutDir)\Etna.QA.SpecFlow.Examples\Etna.QA.SpecFlow.Examples.csproj""C:\Program Files (x86)\TechTalk\SpecFlow\specflow.exe"

It is worth paying attention to the / domain: multiple flag . It tells NUnit to run the assembly from the folder in which it is located. Otherwise, problems with configs may occur.

As a result, we get such a report




Scheduled launch in Team City

In the build setup, you need to specify new artifact: our execution report:


Instead of the step with starting NUnit, we will use the msbuild script that we wrote earlier:

A new report tab will appear in Team City. Here it looks like this:



Non-automated scripts are shown in violet, successfully completed, passed in green, and error tests in red.
All that remains is to put a trigger to run the tests on a schedule.

Task Tracker Sync

TechTalk offers its commercial SpecLog product for managing requirements and communicating with task trackers. He did not suit us for a number of reasons. I am currently working on transparently linking SpecFlow test cases with TFS test cases. With Update 2, tags appeared in TFS. The idea of ​​using conventions in the annotation to communicate with the task tracker seems interesting, for example: @ b8924 , tc345 . As soon as a working solution appears, I will write about it.

Technical aspects of working with Selenium WebDriver and SpecFlow

Described in this article .

Read Next