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