Wednesday, February 27, 2008

Unit Testing of database applications is a large topic, so I'll concentrate on a specific scenario.

If you have the following setup:

TestFixture

  • Init - populate database with default data
  • Teardown - run delete script to restore the database to it's previous state.

This will be sufficient if none of our tests modify the data in between.  But we'll need to to test Create, Update and Delete methods of our business objects.

So, if one Test updates the database, but another Test is run after it which is expecting the database to be in it's original state (i.e. the state it's in straight after the TestFixture.Init() method has been called)  then we have a problem.  But we can't determine the order that the tests will run in, so what are our options?

We could partition our tests into different TestFixtures, so avoiding the problem altogether.  Or we could run the Populate and Delete scripts after EVERY test - though this is likely to be slow and will probably become impractical as our test library grows.

A good solution would be to have each test run in isolation, where changes to the database are rolled back after each test is run - this way the database can be updated in one test but returned to it's original state before the next test runs.

We can do this by using the TransactionScope object, like so:

[Test]
private void TestSomething()
{
  using(new TransactionScope())
  {
    //Update the database
    //Perform Assertions
  }
}

If we need to do some common initialisation of the TransactionScope object, for example setting IsolationLevel or Timeout properties, then we can wrap this up in a class as follows:

    public class RollbackTransaction: IDisposable
    {
        private TransactionScope mScope;

        public RollbackTransaction()
        {
            TransactionOptions tOptions = new TransactionOptions();
            tOptions.IsolationLevel = IsolationLevel.RepeatableRead;
            tOptions.Timeout = new TimeSpan(2, 0, 0);
            mScope = new TransactionScope(TransactionScopeOption.RequiresNew,
                                                          tOptions);
        }

        ///<summary>
        ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        ///</summary>
        ///<filterpriority>2</filterpriority>
        public void Dispose()
        {
            mScope.Dispose();
        }
    }

and instead of using(new TransactionScope()), we would instead have using(new RollbackTransaction()).

 

 

 

Wednesday, February 27, 2008 8:00:59 PM (GMT Standard Time, UTC+00:00) | Comments [0] | TDD | Database#
Tuesday, February 26, 2008

I've been working on a particularly tricky problem recently, one that has me analysing the Form values collection that is passed in an ASP postback.  Not that difficult in itself, but I don't know what values I'm looking for until runtime!

I ended up encapsulating this parsing logic in a special FormValuesParser class, but once I had finished I couldn't eliminate the bad code smell that was hanging around, and coming from someone who buys the office air-freshener, I had to do something about it.

It was easy enough to see where it was coming from.  The main method in this class, ParseKeyType, was a huge switch statement.  Each case block did more or less the same thing, but had to do it in subtly different ways - for example, parse the formValueKey one way for a Browse type, and a different way for a MultiSelectCheck type.

The code looked like this:

private void ParseKeyType(string formValueKey, KeyType keyType)
        {
            string controlMapKey;
            FormKeyInfo keyInfo;
            switch (keyType)
            {
                case KeyType.Browse:
                  //Perform specific parsing to Get Control Map Key
                  //Get Key Info
                  //if KeyInfo != null
                  //Set KeyInfo.Value
                    //Get key look up in ControlMap
                    break;
                case KeyType.MultiSelectCheck:
                  //Perform specific parsing to Get Control Map Key
                  //Get Key Info
                  //if KeyInfo != null
                  //Set KeyInfo.Value
                    //Get key look up in ControlMap
                    break;
                case KeyType.TextEdit:
                case KeyType.Memo:
                case KeyType.SpinEdit:
                case KeyType.Browse_DropDown:
                case KeyType.DateEdit:
                case KeyType.DateTimeEdit:
                  //Perform specific parsing to Get Control Map Key
                  //Get Key Info
                  //if KeyInfo != null
                  //Set KeyInfo.Value
                    //Get key look up in ControlMap
                    break;
                case KeyType.CheckEdit:
                  //Perform specific parsing to Get Control Map Key
                  //Get Key Info
                  //if KeyInfo != null
                  //Set KeyInfo.Value
                    //Get key look up in ControlMap
                    break;
                case KeyType.TimeEdit:
                  //Perform specific parsing to Get Control Map Key
                  //Get Key Info
                  //if KeyInfo != null
                  //Set KeyInfo.Value
                    break;                    
            }

So trying to fathom what was going on when you have to add a new KeyType in 6 months time was going to be a hassle.

If we apply the Strategy pattern, where we encapsulate the code logic within distinct Strategy classes but provide a common interface, we end up with the following, MUCH more fragrant code:

        private void ParseKeyType(string formValueKey, KeyType keyType)
        {
            IKeyInfoStrategy keyInfoStrategy = null;
            switch (keyType)
            {
                case KeyType.Browse:
                    keyInfoStrategy = new BrowseKeyInfoStrategy();
                    break;
                case KeyType.MultiSelectCheck:
                    keyInfoStrategy = new MultiSelectCheckKeyInfoStrategy();
                    break;
                case KeyType.TextEdit:
                case KeyType.Memo:
                case KeyType.SpinEdit:
                case KeyType.Browse_DropDown:
                case KeyType.DateEdit:
                case KeyType.DateTimeEdit:
                    keyInfoStrategy = new SimpleKeyInfoStrategy();
                    break;
                case KeyType.CheckEdit:
                    keyInfoStrategy = new CheckEditKeyInfoStrategy();
                    break;
                case KeyType.TimeEdit:
                    keyInfoStrategy = new TimeEditKeyInfoStrategy();
                    break;        
            }


            if (keyInfoStrategy != null)
            {
                string controlMapKey = keyInfoStrategy.GetControlMapKey(formValueKey);
                FormKeyInfo keyInfo = GetKeyInfo(controlMapKey);
                keyInfoStrategy.SetKeyInfoValue(keyInfo, formValueKey,
                                                    mFormValueCollection);            
            }
        }

First off, we create an IKeyInfoStrategy interface which contains the common behaviour we have abstracted, and then a concrete Strategy class for each different 'class' of KeyType.

 

This is a very simple example, but you can see how it will scale and lead to much easier-to-read code.

 

Tuesday, February 26, 2008 8:48:16 PM (GMT Standard Time, UTC+00:00) | Comments [0] | Patterns#
Saturday, February 16, 2008

Hello, and welcome to my first blog.  This has been something I've been meaning to get around to for a while now, so hopefully will get the chance to keep it up-to-date.

This will be very much a practical blog in programming, basically outlining the problems I face from day-to-day and how I go about solving them.  No doubt there are better solutions than the ones I have come up with - so let me know where I can make improvements!

 

 

 

Saturday, February 16, 2008 9:10:28 PM (GMT Standard Time, UTC+00:00) | Comments [0] | #
Search
Archive
Links
Categories
Admin Login
Sign In
Blogroll