Following on from the BDD articles I will cover how we have been using BDD development techniques to develop BizTalk maps. A BizTalk map is a discreet component in BizTalk which is ideally suited to development based on desired behaviours from the component. It fits well with our practices. If you imagine we are at a point where we have worked with our Business Analyst to work out the data formats required for various systems then we are in a position to work with them to begin understanding how the data transformation between systems needs to take place.
Using the Specflow feature file and gherkin syntax we have talked about previously gives us an excellent way to begin looking at the behaviour required from the map. In this scenario the maps behaviour is documented in the specflow example below. Note that this will be a very simple transformation.
Feature: Map System A Person to System B Person
In order to load data into System B
As a integration server
I want to transform a person from the system A format to the system B format
Scenario: Default Scenario
Given I have a person from System A
| Firstname | Surname |
| Joe | Bloggs |
When I execute the map
Then the person in systems B full name should be JOE BLOGGS
Scenario: Person has no first name
Given I have a person from System A
| Firstname | Surname |
|
| Bloggs |
When I execute the map
Then the person in systems B full name should be BLOGGS
Scenario: Person has no surname
Given I have a person from System A
| Firstname | Surname |
| Joe |
|
When I execute the map
Then the person in systems B full name should be JOE With A Space
Scenario: Person has no name
Given I have a person from System A
| Firstname | Surname |
|
|
|
When I execute the map
Then the person in systems B full name should be empty
In this example you can see that I am using the table feature in Specflow to provide data from the specification which we will use behind the scenes in our test implementation to execute the test cases.
Building the Test
We now have a clear view of what the transformation needs to do so the Business Analyst can let us get on with developing our test implementation and then the map which we will develop again with the red/green/refactor process. The test implementation for this case will look something like the following.
In this test we will be using classes generated from the schemas and then populate the objects with data from the Specflow test. This gives us the excellent benefit of strong typing around the test data used with the map so that if the schema changes then we are able to catch compile errors and deal with this in a nice easy to handle way. Once we have the input class we will serialize it and use it to input into the map. We will then take the output from the map and deserialize it to the output object from where we will be able to use assertions against the object in the normal .net way.
A sample of the code behind this test is below:
using System.IO;
using System.Xml.Serialization;
using BizTalkStuff.Messages;
using Microsoft.BizTalk.TestTools.Mapper;
using Microsoft.BizTalk.TestTools.Schema;
using TechTalk.SpecFlow;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test
{
[Binding]
public
class
StepDefinitions
{
private
readonly
SystemAPerson _systemAPerson = new
SystemAPerson();
private
SystemBPerson _systemBPerson = new
SystemBPerson();
[Given(@"I have a person from System A")]
public
void GivenIHaveAPersonFromSystemA(Table table)
{
var firstName = table.Rows[0]["Firstname"];
var surname = table.Rows[0]["Surname"];
_systemAPerson.Firstname = firstName;
_systemAPerson.Surname = surname;
}
[Then(@"the person in systems B full name should be (.*)")]
public
void ThenThePersonInSystemsBFullNameShouldBe(string name)
{
if (name == "JOE With A Space")
name = "JOE ";
if(name == "empty")
name = string.Empty;
Assert.AreEqual(name, _systemBPerson.Fullname);
}
[When(@"I execute the map")]
public
void WhenIExecuteTheMap()
{
const
string testInputFileName = @"c:\maptestinput.xml";
const
string outputFileName = @"c:\maptestoutput.xml";
SerializeInput(testInputFileName, _systemAPerson);
TestableMapBase map = new BizTalkStuff.SystemAPerson_To_SystemBPerson();
map.TestMap(testInputFileName, InputInstanceType.Xml, outputFileName, OutputInstanceType.XML);
_systemBPerson = DeserializeOutput<SystemBPerson>(outputFileName);
}
private
static
void SerializeInput(string filePath, object o)
{
using (var stream = new
FileStream(filePath, FileMode.Create, FileAccess.Write))
{
var serializer = new
XmlSerializer(o.GetType());
serializer.Serialize(stream, o);
stream.Flush();
}
}
private
static T DeserializeOutput<T>(string filePath)
{
using (var stream = new
FileStream(filePath, FileMode.Open, FileAccess.Read))
{
var serializer = new
XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stream);
}
}
}
}
Building the Map
Before you build the tests you will have created your schema and generated the .net messages to help you write the tests, but you can now drive the TDD approach to building the map using the tests you have created. In this scenario the map was very simple and looked like the following:
Conclusion
Hopefully you can see that BizTalk maps are another good candidate where a BDD approach involving the use of Specflow can significantly reduce the total cost of ownership of the map because you have documentation of what the map is supposed to do in plain business like language. This was obviously a very simple sample but if you imagine some of the big maps you often come across where it's a complete nightmare to understand anything about them, this approach could really make life simpler for no additional effort.
The sample for this post is at the following location: https://s3.amazonaws.com/CSCBlogSamples/SpecFlowMapTest.zip
Recently we have been looking at changing the way we develop and test BizTalk Business Rules to use a BDD approach and our tool of choice to help with this is Specflow. I wanted to talk through how we do this and also why I think its useful.
I feel Business Rules are an excellent candidate for the BDD style approach to development that Specflow encourages.
It also promotes a TDD style where you can work with your BA to nicely document your rule requirements as you test and implement them. Some of the benefits offered by Specflow include:
- The specflow test can act as scenario based documentation over how the rule is supposed to work
- The documentation would be tested and therefore always accurate as part of your ALM process
- The specflow test makes it much easier to maintain the rule
Ive blogged many times about the various benefits of specflow so lets look at a walk-through of this way of working.
The Specflow Feature File
First off I go into my Visual Studio solution and in an MsTest project and add the configuration to tell Specflow that I am using MsTest in Visual Studio 2010.
<?xml
version="1.0"
encoding="utf-8" ?>
<configuration>
<configSections>
<section
name="specFlow"
type="TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler, TechTalk.SpecFlow"/>
</configSections>
<specFlow>
<unitTestProvider
name="MsTest.2010" />
</specFlow>
</configuration>
Next I add a Specflow feature file.
Once I have my new feature I can begin working with my business analyst to document the desired rule behaviour. In the example below we are looking at a business rule which is used to evaluate an employees performance to decide if they should be awarded a promotion. The Gherkin for this looks something like the below:
Feature: Employee Promotion Rules
In order to manage staff promotions
As a hr manager
I want to be able to calculate if an employee should be promoted or not
Scenario: Level 4 Employee should not be promoted regardless of score
Given I have an employee called Joe
And the employees current level is Level 4
And the employees current performance is 100
When The employees apraisal is evaluated by the promotion rules
Then the employees appraisal score should be 100
And the employee should not be recommended for promotion
Scenario: Level 1 Employee should not be promoted
Given I have an employee called Joe
And the employees current level is Level 1
And the employees current performance is 25
When The employees apraisal is evaluated by the promotion rules
Then the employees appraisal score should be 25
And the employee should not be recommended for promotion
Scenario: Level 1 Employee should promoted
Given I have an employee called Joe
And the employees current level is Level 1
And the employees current performance is 26
When The employees apraisal is evaluated by the promotion rules
Then the employees appraisal score should be 26
And the employee should be recommended for promotion
Scenario: Level 2 Employee should not be promoted
Given I have an employee called Joe
And the employees current level is Level 2
And the employees current performance is 50
When The employees apraisal is evaluated by the promotion rules
Then the employees appraisal score should be 50
And the employee should not be recommended for promotion
Scenario: Level 2 Employee should promoted
Given I have an employee called Joe
And the employees current level is Level 2
And the employees current performance is 51
When The employees apraisal is evaluated by the promotion rules
Then the employees appraisal score should be 51
And the employee should be recommended for promotion
Scenario: Level 3 Employee should not be promoted
Given I have an employee called Joe
And the employees current level is Level 3
And the employees current performance is 75
When The employees apraisal is evaluated by the promotion rules
Then the employees appraisal score should be 75
And the employee should not be recommended for promotion
Scenario: Level 3 Employee should promoted
Given I have an employee called Joe
And the employees current level is Level 3
And the employees current performance is 76
When The employees apraisal is evaluated by the promotion rules
Then the employees appraisal score should be 76
And the employee should be recommended for promotion
As you can see from the above it is very obvious what the expected behaviour should be from this rule which means I can be quite clear what I am supposed to develop. Also if you imagine that we completed the project and left it for 6 months then came back to it we could easily see what the rule is supposed to do.
Implementing the test behind the feature file
The next step is to implement the C# code which will be executed by the specflow test. In the code snippet below you can see the code which I have put into a C# file which the Specflow test will execute based on the given/when/then attribute usage. One of the things you may note is the use of wild cards in Specflow which make it easy to reuse the same step method to execute multiple gherkin statements. An example of this is in the GivenTheEmployeesCurrentLevelIs method where the wild card is used so that we can pass in the level from the gherkin in the feature file.
In the sample we have a simple employee and employee appraisal object which are populated with some data then ran through the business rule policy which will evaluate conditions and then amend the objects accordingly. After the policy is executed the test will make some assertions about how the objects should have been changed.
using TechTalk.SpecFlow;
using Acme.SpecFlow.BRETests.Rules.Facts;
using Microsoft.RuleEngine;
using MsTest = Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test
{
[Binding]
public
class
EmployeePromotionSteps
{
private
Employee _employee = new
Employee();
private
EmployeeAppraisal _appraisal = new
EmployeeAppraisal();
[Given(@"I have an employee called (.*)")]
public
void GivenIHaveAnEmployeeCalled(string name)
{
_employee.Name = name;
}
[Given(@"the employees current level is (.*)")]
public
void GivenTheEmployeesCurrentLevelIs(string level)
{
_employee.Level = level;
}
[Given(@"the employees current performance is (.*)")]
public
void GivenTheEmployeesCurrentPerformanceIs(int performance)
{
_employee.CurrentPerformance = performance;
}
[Then(@"the employee should not be recommended for promotion")]
public
void ThenTheEmployeeShouldNotBeRecommendedForPromotion()
{
MsTest.Assert.IsFalse(_appraisal.RecommendPromotion, "The employee should not be recommended for promotion");
}
[Then(@"the employees appraisal score should be (.*)")]
public
void ThenTheEmployeesAppraisalScoreShouldBe(int appraisalScore)
{
MsTest.Assert.IsTrue(_appraisal.AppraisalScore == appraisalScore, "The appraisal score is incorrect");
}
[Then(@"the employee should be recommended for promotion")]
public
void ThenTheEmployeeShouldBeRecommendedForPromotion()
{
MsTest.Assert.IsTrue(_appraisal.RecommendPromotion, "The employee should be recommended for promotion");
}
[When(@"The employees apraisal is evaluated by the promotion rules")]
public
void WhenTheEmployeesApraisalIsEvaluatedByThePromotionRules()
{
var policy = new
Policy("Specflow.EmployeeAppraisal");
var facts = new
object[2];
facts[0] = _employee;
facts[1] = _appraisal;
policy.Execute(facts);
policy.Dispose();
}
}
}
This scenario is a very simple business rule to illustrate the technique but it should be possible to use the approach with any rule you want to develop. If you can test it with C# then you can test it with specflow.
Developing the Rule
At this point we have developed our specification for the behaviour of the rule and also the test cases which should prove the rule works and we can begin using red/green/refactor development techniques to build our rule. Lets start by developing the default rule for the default case.
Default Rule
In the default rule it's a bit like an else clause. Based on the specification of behaviour we will be focusing on the scenario where the employee is not level 2 or 3. We aim to prove that a level 4 employee would not be promoted regardless of their performance score.
The rule would look something like the below picture.
When we have added the implementation to the rule we should now be able to begin executing the test case for the level 4 employee and see that they are not promoted even if they score 100 in their performance. If we make any errors when implementing this rule we can work through these until the test goes green.
Adding the Level 1 Employee Rule
When developing the level 1 rule we are now focusing on the two test cases which test the level 1 employee. If we run these tests we will find that the not recommended for promotion test will pass straight away but the recommend for promotion test fails so we will add a new rule to the policy to check that a level 1 employee should be promoted if they score more than 25 points. The rule for this would look something like the below picture.
The two tests we will work with check that the employee is only promoted if they score 25 points or more. We should also rerun the test case for the default rule to make sure out level 4 employee is still not promoted.
Adding the Level 2 & 3 Rules
We would then continue to add the rules for the level 2 and 3 employees the same as we did for the level 1 employee ensuring we work through any errors as we add the rules and also check that the other tests still pass.
At the end of the implementation of the rules we may want to revisit the original specflow definition and to see if we need to add any additional scenarios to make sure we have adequately covered things like boundary conditions etc.
Conclusion
As you can see once our core cases are in place we are really just reusing the same specflow gherkin statements to add additional test cases and we can end up with a very solid set of test cases for our business rule ensuring that it is thourghly tested in isolation before we start trying to use the rule from within BizTalk which means that we should have no issues calling the rule from an orchestration as we know the rule executes exactly how we expect it too before we use it in our wider solution implementation.
Hopefully you can see combining Specflow with the traditional way of testing business rules from .net code adds a lot of additional value and lowers the cost of ownership of your code without requiring anymore effort.
The Sample
The sample is available at the following location: https://s3.amazonaws.com/CSCBlogSamples/SpecFlowBRETest.zip
In the sample you will find the xml file called BRERulePolicy.xml which contains the rule policy used in this example and you can then deploy that and play around with the tests. Please note that there is an msbuild snippet in the C# project which deploys the assembly to the GAC during compile so you may need to modify that to the location for gacutil on your machine.