Spec Flow and Friends 3

Following on from my last post I now have a nice failing scenario in Spec Flow

image

I could just rush in and start writing production code to make the scenario pass, but this would be a mistake, scenarios by their nature tend to be at a higher level covering a flow of work through the whole system, so I will probably have to write a ‘lot’ of code to make the scenario pass, this is not very incremental, I always like to have working code, so any changes or additions I make have to be small.

To achieve these small incremental changes I need a finer gained framework to guide and assist me, I have chosen Machine Specifications, primarily because I like its output, it produces sentence like structures making the reports very easy to read and the style fits well with the textural nature of the spec flow fixtures, also the framework forces a nice code structure, which makes the completed tests easy to read and understand.  But any unit test framework could be used, the idea is that these smaller tests are there to support the scenario and feature tests.

A quick lap around Machine Specifications.

Unlike other frameworks Machine Specifications doesn’t include the word test, instead it uses the idea of asserting behaviour, and aims to produce a report that reads as discrete sentences that describe the expected behaviour. 

  1. [Subject("the subject of this specification")]
  2. public class describe_the_behaviour_we_are_specifying
  3. {
  4.     Establish context =
  5.         () =>
  6.             {
  7.                 // do any setup that is necessary
  8.             };
  9.  
  10.     Because of =
  11.         () =>
  12.             {
  13.                 // the action that causes the behaviour, this should be a single line of code
  14.             };
  15.  
  16.     It causes_this_result_to_happen =
  17.         () =>
  18.             {
  19.                 // specify the expected result of the action, this should be a single line of code
  20.             };
  21.  
  22.     It also_causes_this_to_happen =
  23.         () =>
  24.             {
  25.                 // specify other expected results, this should be a single line of code
  26.             };    
  27. }

 

The subject line is either some text describing the subject of the behaviour or a typeof(some class) statement.

The class name describes the behaviour we are testing, within the class there are three allowed delegate expressions Establish, Because and It. 

Establish context is used to setup the environment that the objects will operate in, this includes the subject of the test.

Because of is used to call the single method, event or property that the behaviour dictates, this should be a single action, if multiple actions are required, they should be specified separately in different classes.

The It delegate is used to assert the results of the Because action, there can be one or more of these within each class, each should be a single line of code.

Running these specifications produce the following results.

image

This is where the Machine Specifications framework starts to shine, by careful naming of the subject, class and It clauses, a distinct description can be build simply by running the tests.

A live example

In my previous post I showed the feature and step definition files for a home project I was working on, the first start up scenario looked like this

image

the first and second asserts can be easily done with simple one liners, but from the third third line onwards requires that I read a solution file, and discover what projects are there and print out the names to the console, which is a bit more code than a single line of code, so at this point I dropped down to machine specifications.

I created a test solution file that I could use to within the tests this allowed me to control the paths to projects and other data, this solution file was added to the test project and set to always copy to the output directory during a build. It is always good to control the data coming into an application during testing as it ensures that noting external to the test can effect the results.

  1. [Subject("Solution File")]
  2. public class when_the_solution_file_is_loaded
  3. {
  4.     Establish context =
  5.         () =>
  6.         {
  7.             string currentDirectory = Directory.GetCurrentDirectory();
  8.             _solutionPath = Path.Combine(currentDirectory, "TestSolution.sln");
  9.  
  10.             _solution = new Solution();
  11.         };
  12.  
  13.     Because of =
  14.         () =>
  15.         {
  16.             _solution.Load(_solutionPath);
  17.         };
  18.  
  19.     It contains_the_name_of_the_solution =
  20.         () => _solution.Name.ShouldEqual("TestSolution.sln");
  21.  
  22.  
  23.     static Solution _solution;
  24.     static string _solutionPath;
  25. }

 

The above test creates a file path to this solution file, then loads the file and ensures that the solution name is set, I decided to create a solution object that would read the solution file and extract the required data using simple regular expressions, because the solution file is not actually valid xml, and cannot be easily parsed in another way.

After getting this simple test to pass, I continued in the same vain to extract the project file paths and names which are then passed back up to the main application for display making the scenario pass completely.

  1. It contains_two_project_files =
  2.     () => _solution.Projects.Count().ShouldEqual(2);
  3.  
  4. It first_project_file_is_TestProject1 =
  5.     () => (from p in _solution.Projects where p.ProjectPath == "TestProject1.csproj" select p).FirstOrDefault().ShouldNotBeNull();
  6.  
  7. It second_project_file_is_TestProject2 =
  8.     () => (from p in _solution.Projects where p.ProjectPath == "TestProject2.csproj" select p).FirstOrDefault().ShouldNotBeNull();
  9.  
  10. It first_project_file_name_is_TestProject1 =
  11.     () => (from p in _solution.Projects where p.ProjectName == "TestProject1.csproj" select p).FirstOrDefault().ShouldNotBeNull();
  12.  
  13. It second_project_file_name_is_TestProject2 =
  14.     () => (from p in _solution.Projects where p.ProjectName == "TestProject2.csproj" select p).FirstOrDefault().ShouldNotBeNull();

 

Having the feature file and its scenarios guides the generation of the lower level machine specifications, making the tests easier to write because at the time of writing you are attempting to solve a known problem, also the nature of the testing has changed, now only worrying about the output from a function or property rather than how that outcome is achieved, allows the internal structure of a class to be refactored without breaking the tests.

Using this outside in method of development achieves a higher test coverage of the code, with fewer tests, because the features provide the over all frame for the development process, leaving the machine specifications to perform a supporting role where details are necessary.

Knowing when to drop down to the lower level is important, I have formed a simple guideline that I follow

if I can’t make the scenario pass with a single line of code, either directly or by calling an already existing method, then I will drop down to the machine specifications.

Which ensures that I don’t go off writing production code without the assistance of the testing framework.

Advertisements

About Duncan Butler

Trying to be a very agile software developer, working in C# with Specflow, Nunit and Machine Specifications, and in the evening having fun with Ruby and Rails
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s