I was attending a conference six months ago and listened to a talk about quality. During the talk, I was introduced to EARS — Easy Approach to Requirements Syntax. This way of writing requirements struck a chord with me, given my prior experience reading and writing requirement specifications.

When doing agile development, we write User Stories and define Acceptance Criteria.

As a <role>, I want <goal/desire> so that <benefit>

When doing BDD, we follow this format:

In order to <receive benefit>, as a <role>, I want <goal/desire>

It’s not always easy to go from user stories and acceptance criteria to start writing tests.

I think that with Easy Approach to Requirements Syntax in place, it will be easier to do Behavior Driven Development.

When doing Hypothesis Driven Development, we follow this format:

We believe <this capability>

Will result in <this outcome>

We will know we have succeeded when <we see a measurable signal>

So my hypothesis is:

I believe using Easy Approach to Requirements Syntax

Will result in easier implementation of Behavior Driven Development

I will know I have succeeded when business people can actually write the (SpecFlow) feature files themselves ☺

Easy Approach to Requirements Syntax

EARS was created during a case study at Rolls-Royce on requirements for aircraft engine control systems.

They identified eight major problems with writing requirements in an unstructured natural language:

  • Ambiguity (a word or phrase has two or more different meanings).
  • Vagueness (lack of precision, structure and/or detail).
  • Complexity (compound requirements containing complex sub-clauses and/or several interrelated statements).
  • Omission (missing requirements, particularly requirements to handle unwanted behavior).
  • Duplication (repetition of requirements that are defining the same need).
  • Wordiness (use of an unnecessary number of words).
  • Inappropriate implementation (statements of how the system should be built, rather than what it should do).
  • Untestability (requirements that cannot be proven true or false when the system is implemented).

To overcome or reduce the effects of these problems they came up with a rule set with five simple templates.

Requirements are divided into five types:

  • Ubiquitous
  • Event-driven
  • State-driven
  • Unwanted behaviors
  • Optional features

Ubiquitous

The <system name> shall <system response>

Event-driven

When <optional preconditions> <trigger>, the <system name> shall <system response>

State-driven

While <in a specific state>, the <system name> shall <system response>

Unwanted behaviors

If <optional preconditions> <trigger>, then the <system name> shall <system response>

Optional features

Where <feature is included>, the <system name> shall <system response>

The Stack Class

Let’s put this to the test with the Stack<T> Class as the example.

This is some of the documentation from MSDN:

Represents a variable size last-in-first-out (LIFO) collection of instances of the same specified type.

The capacity of the Stack<T> is the number of elements that the Stack<T> can store. Count is the number of elements that are actually in the Stack<T>.

Three main operations can be performed on a Stack<T> and its elements:

  • Push inserts an element at the top of the Stack<T>.
  • Pop removes an element from the top of the Stack<T>.
  • Peek returns an element that is at the top of the Stack<T> but does not remove it from the Stack<T>.

If we were to write a User Story in BDD format:

In order to store instances of the same specified type in last-in-first-out (LIFO) sequence

As a developer

I want to use a Stack<T>

If we were to write requirements with EARS templates:

Ubiquitous

The Stack<T> shall store instances of the same specified type in last-in-first-out (LIFO) order.

The Stack<T> shall return the number of elements contained when the property Count is invoked.

Event-driven

When the method Push is invoked, the Stack<T> shall insert the element at the top.

When the method Pop is invoked, the Stack<T> shall remove and return the element at the top.

When the method Peek is invoked, the Stack<T> shall return the element at the top without removing it.

State-driven

While an element is present, the Stack<T> shall return true when the method Contains is invoked.

While an element is not present, the Stack<T> shall return false when the method Contains is invoked.

Unwanted behaviors

If empty and the method Pop is invoked, then the Stack<T> shall throw InvalidOperationException.

If empty and the method Peek is invoked, then the Stack<T> shall throw InvalidOperationException.

Optional features

Where instantiated with a specified collection, the Stack<T> shall be prepopulated with the elements of the collection.

Behavior Driven Development

Let’s take this to the next level with BDD and SpecFlow.

Feature: Stack<T>
In order to store instances of the same specified type in last-in-first-out (LIFO) sequence
As a developer
I want to use a Stack<T>
@Ubiquitous
Scenario: The Stack<T> shall store instances of the same specified type in last-in-first-out (LIFO) order
Given an empty stack
When the pushing the elements:
| Element |
| One |
| Two |
| Three |
Then the elements should be popped in the order:
| Element |
| Three |
| Two |
| One |
@Ubiquitous
Scenario: The Stack<T> shall return the number of elements contained when the property Count is invoked
Given a stack with the elements:
| Element |
| One |
| Two |
| Three |
When counting the elements
Then the result should be 3
@EventDriven
Scenario: When the method Push is invoked, the Stack<T> shall inserts the element at the top
Given a stack with the elements:
| Element |
| One |
| Two |
| Three |
When pushing the element "Four"
Then the element "Four" should be on top
@EventDriven
Scenario: When the method Pop is invoked, the Stack<T> shall remove and return the element at the top
Given a stack with the elements:
| Element |
| One |
| Two |
| Three |
When popping the element on top
Then the result should be "Three"
And the element "Two" should be on top
@EventDriven
Scenario: When the method Peek is invoked, the Stack<T> shall return the element at the top without removing it
Given a stack with the elements:
| Element |
| One |
| Two |
| Three |
When peeking at the element on top
Then the result should be "Three"
And the element "Three" should be on top
@StateDriven
Scenario: While an element is present, the Stack<T> shall return true when the method Contains is invoked
Given a stack with the elements:
| Element |
| One |
| Two |
| Three |
When determining if the stack contains the element "Three"
Then the result should be true
@StateDriven
Scenario: While an element is not present, the Stack<T> shall return false when the method Contains is invoked
Given a stack with the elements:
| Element |
| One |
| Two |
| Three |
When determining if the stack contains the element "Four"
Then the result should be false
@UnwantedBehavior
Scenario: If empty and the method Pop is invoked, then the Stack<T> shall throw InvalidOperationException
Given an empty stack
When popping the element on top
Then InvalidOperationException should be thrown
@UnwantedBehavior
Scenario: If empty and the method Peek is invoked, then the Stack<T> shall throw InvalidOperationException
Given an empty stack
When peeking at the element on top
Then InvalidOperationException should be thrown
@Optional
Scenario: Where instantiated with a specified collection, the Stack<T> shall be pre-populated with the elements of the collection
Given a stack with the elements:
| Element |
| One |
| Two |
| Three |
Then the stack should contain the elements:
| Element |
| Three |
| Two |
| One |
view raw Stack.feature hosted with ❤ by GitHub
  • Each requirement has its own scenario
  • I’ve tagged the scenarios with the type of requirement for clarity
using Should;
using System;
using System.Collections.Generic;
using System.Linq;
using TechTalk.SpecFlow;
namespace ConductOfCode.Steps
{
[Binding]
public class StackSteps
{
private Stack<string> stack;
private string result;
private int count;
private bool contains;
private Exception exception;
[Given(@"an empty stack")]
public void GivenAnEmptyStack()
{
stack = new Stack<string>();
}
[Given(@"a stack with the elements:")]
public void GivenAStackWithTheElements(Table table)
{
var elements = table.Rows.Select(x => x.Values.First()).ToList();
stack = new Stack<string>(elements);
}
[When(@"the pushing the elements:")]
public void WhenThePushingTheElements(Table table)
{
var elements = table.Rows.Select(x => x.Values.First()).ToList();
foreach (var element in elements)
{
stack.Push(element);
}
}
[When(@"counting the elements")]
public void WhenCountingTheElements()
{
count = stack.Count;
}
[When(@"pushing the element ""(.*)""")]
public void WhenPushingTheElement(string element)
{
stack.Push(element);
}
[When(@"popping the element on top")]
public void WhenPoppingTheElementOnTop()
{
try
{
result = stack.Pop();
}
catch (Exception ex)
{
exception = ex;
}
}
[When(@"peeking at the element on top")]
public void WhenPeekingAtTheElementOnTop()
{
try
{
result = stack.Peek();
}
catch (Exception ex)
{
exception = ex;
}
}
[When(@"determining if the stack contains the element ""(.*)""")]
public void WhenDeterminingIfTheStackContainsTheElement(string element)
{
contains = stack.Contains(element);
}
[Then(@"the elements should be popped in the order:")]
public void ThenTheElementsShouldBePoppedInTheOrder(Table table)
{
var elements = table.Rows.Select(x => x.Values.First()).ToList();
foreach (var element in elements)
{
element.ShouldEqual(stack.Pop());
}
}
[Then(@"the result should be (.*)")]
public void ThenTheResultShouldBe(int expected)
{
count.ShouldEqual(expected);
}
[Then(@"the element ""(.*)"" should be on top")]
public void ThenTheElementShouldBeOnTop(string element)
{
element.ShouldEqual(stack.Peek());
}
[Then(@"the result should be ""(.*)""")]
public void ThenTheResultShouldBe(string expected)
{
result.ShouldEqual(expected);
}
[Then(@"the result should be true")]
public void ThenTheResultShouldBeTrue()
{
contains.ShouldBeTrue();
}
[Then(@"the result should be false")]
public void ThenTheResultShouldBeFalse()
{
contains.ShouldBeFalse();
}
[Then(@"InvalidOperationException should be thrown")]
public void ThenInvalidOperationExceptionShouldBeThrown()
{
exception.ShouldBeType<InvalidOperationException>();
}
[Then(@"the stack should contain the elements:")]
public void ThenTheStackShouldContainTheElements(Table table)
{
var elements = table.Rows.Select(x => x.Values.First()).ToList();
foreach (var element in elements)
{
stack.Contains(element).ShouldBeTrue();
}
}
}
}
view raw StackSteps.cs hosted with ❤ by GitHub

In my opinion, it was easy to write the tests. I copy-and-pasted the requirement to the SpecFlow feature file and then I knew exactly how many scenarios I needed to implement. I think the examples in the scenarios makes the requirements easier to understand and reason about. Maybe this should be called Requirements by Example?

The BDD Cycle

When implementing the production code, we can use The BDD Cycle described in The RSpec Book.

The BDD Cycle

  • A photo of page 10 from my copy of The RSpec Book
  • As a .NET developer, you can replace Cucumber with SpecFlow and RSpec with Machine.Specifications

The BDD Cycle introduces two levels of testing. We can use SpecFlow to focus on the high-level behavior, the requirements. And use Machine.Specifications to focus on more granular behavior, unit testing code in isolation.

Resources