I’ve been working on integrating FitNesse and FIT into my current project. As with all automated testing, managing test data is a challenge.
To manage the data we have 3 options:
- Abstract the data from the system
- Restore the data to a known state before the tests run
- Create a reciprocating transaction. i.e. Each insert has a corresponding delete
- Rollback the test when it’s completed.
Option 1 is great, but it is difficult to abstract a system from the database especially if you are working with a pre-existing (legacy) system.
Option 2 is viable, but I hate having to do a database restore before I run a test suite.
I’m too lazy for option 3 and wouldn’t pick this option if there are a slew of user acceptance tests(UAT).
So I want to use option 4. Roy Osherove wrote a great article describing how to manage your unit tests using COM+ enterprise services and I have been using this method ever since, however, my tests use “Services Without Components” (SWC) included in COM+ 1.5.
I figured I could you the same concept with FitNesse! My solution is very simple. I created a ColumnFixture that can begin a transaction, commit a transaction, but most importantly rollback a transaction. The Fixture has 2 columns, Action and Result?. The Action column take one of three values: “begin”, “rollback”, and “commit”. The Result? column can be left blank. It will get populated with the System.EnterpriseServices.TransactionStatus after the Result() method is executed.
Now that I have this ColumnFixture, I created two pages: BeginTransaction and RollbackTransaction. These pages will be included before and after, any test fixtures that I want to run within a transaction. I prefer to use FitNesse’s !include widget, but you could also use the SetUp and TearDown pages.
Here's the code.
/// <summary>
/// This class provides a simple interface to support COM+ transactions.
/// </summary>
public class TransactionFixture : ColumnFixture
{
public string Action = "begin";
public string Result()
{
TransactionStatus retVal = TransactionStatus.NoTransaction;
switch(Action.ToLower())
{
case "begin":
ServiceConfig config = new ServiceConfig();
config.Transaction = TransactionOption.RequiresNew;
ServiceDomain.Enter( config );
break;
case "rollback":
if( ContextUtil.IsInTransaction )
{
ContextUtil.SetAbort();
}
retVal = ServiceDomain.Leave();
break;
case "commit":
if( ContextUtil.IsInTransaction )
{
ContextUtil.SetComplete();
}
retVal = ServiceDomain.Leave();
break;
default:
throw new ArgumentException("Invalid value", "Action");
}
return retVal.ToString();
}
}
Screenshot - Before
Screenshot - After
BeginTransaction Page
|!-GiffordConsulting.FitNesseAid.TransactionFixture-!|
|Action|Result?|
|begin||
RollbackTransaction Page
|!-GiffordConsulting.FitNesseAid.TransactionFixture-!|
|Action|Result?|
|rollback||
Putting it all together using import widget
!include BeginTransaction
|!-GiffordConsulting.FitNesseAid.SignonFixture-!|
|Username|Password|Save?|
|Tim|apassword|true|
!include RollbackTransaction