Service orientated architecture approach to BDD

There are many tutorials and papers on behavioral driven development in the context of the presentation tier, but we needs to address the usage when implementing service orientated architecture. To do this we will work with a real world example of it's usage on a project.

The project in question is an Address Service. The service has three requirements:

  • As a service consumer, when I submit address information, save it.
  • As a service consumer, when I provide an identifier, return addresses
  • As a service consumer, when I provide a country identifier, return a list of fields required for that countries address
  • As a service consumer, when I request, return a list of countries
  • As a service consumer, when I provide a country code, return a country
  • As a service consumer, when I provide the information, save a country You will notice that in this context we list the service consumer in place of a user. This will need to be explained to the business in terms understandable by all stakeholders. We need to turn these requirements in to testable specifications. Firstly lets group our tests as follows:
  1. Retrieve Address
  2. Save Address
  3. Retrieve Country
  4. Save Country So in Visual Studio, lets create a new project (class library) in a solution and name it "AddressService.Specs". Using the correct terminology at this point is key. It very much helps avoid confusion and provide clarity for the project. Within the project create four folders as above.

I know that every address will have a country, as such the address features of the solution will be dependent on the countries features. So we have a natural starting point: the functionality for countries.

Within the Save Country project folder we will create a new class "ValidCountryReceived". Of course this is not a particularly 'friendly' name so lets add a trait name of "A Valid Country is Received". This really helps us relate our tests back to our requirements. Within the new class, create two tests as below:

[Trait("A Valid Country Is Received","")]
    public class ValidCountryReceived
    {
        [Fact(DisplayName = "A country is added to the system")]
        public void Country_Is_Added_To_System()
        {
            throw new NotImplementedException();
        }

        [Fact(DisplayName = "A unique identifier is returned for the address")]
        public void Unique_Identifier_Returned_For_Country()
        {
            throw new NotImplementedException();
        }
    }

Again the tests have been given friendly diaplay names. Sometimes you will see the word 'should' be used in the description of a test outcome. I am not a big fan of wiggle word like 'should', they do not comminucate a certain outcome. I prefer more certain words such as 'will' and 'is'. You will also notice that for the time being we are throwing a not implemented exception, following methodology that our tests should fail and that we add code to make them pass. Both tests are deemed part of the 'happy path' (our successful path). Generally the happy path will change less over the course of development, whereas the unhappy path is likely to be frequently expanding. As development progresses, your happy path should be your regular go to for ensuring you still have a functioning product.

Next we continue to add classes and tests to satisfy our happy criteria as follows:

[Trait("User requests a list of countries","")]
public class RequestsListOfCountries
{
	[Fact(DisplayName = "When a user requests, return a list of coutries")]
	public void ListOfCountriesIsReturned()
	{
		throw new NotImplementedException();
	}
}

[Trait("A Valid Country Code is Submitted", "")]
public class ValidCountryCode
{
	[Fact(DisplayName ="When a user submits a valid country code a country is returned")]
	public void Country_Is_Returned()
	{
		throw new NotImplementedException();
	}
}

With our happy path in place, we need to start adding test for known failure scenarios. We will add additional classes and tests as below:

[Trait("An Invalid Country Code is Submitted","")]
public class InvalidCountryCode
{
	[Fact(DisplayName ="When a user submits an invalid country code a message is returned explaining that the country could not be found")]
	public void Message_Is_Returned_Indicating_Country_Not_Found()
	{
		throw new NotImplementedException();
	}
}

[Trait("An invalid country is received","")]
public class InvalidCountryReceived
{
	[Fact(DisplayName ="When a user submits an invalid country return a list of errors for the data")]
	public void Return_A_List_Of_Errors()
	{
		throw new NotImplementedException();
	}
}

We now have tests written for the first part of our service, that we can directly correlate with the business requirements. This focuses the technical minds on staying on task and producing enough code to meet the tests and thus the business requirements.

While I will refrain from typing the remainder of the tests, they will all follow a similar pattern of being very relatable to the business requirements.