Archive

Archive for the ‘TDD’ Category

Simplify IUserType Testing

October 16, 2009 4 comments

Background

The application I am currently working on makes ( will make ) heavy use of the .NET TimeZoneInfo class to allow us to display times in the current users local time. To achieve this we need to capture and store the timezone from the user.

Capturing the timezone is relatively simple, I use a slightly modified version of this technique used by Nate Kohari’s most excellent Zen.

Storing the timezone provides an interesting choice of options;

I could store the raw offset in hours ( a double ) or in minutes ( an int ) and recreate the TimeZoneInfo each time I want to display a date or time in the users local time. This has the benefit of being incredibly simple and, with a little work to create an extension method, generates almost no duplication except the act of converting the double or int into a TimeZoneInfo.

The second alternative was to let my trusty ORM do the work for me. I use NHibernate and after a little research I found the following articles about creating an IUserType that would allow me to map a TimeZoneInfo into a database field. If you want all the gory details of how to do it go and read those articles, otherwise the code for my TimeZoneInfoUserType is shown at the bottom of the post ( see the section titled The Implementation ).

The Useful Stuff

Normally I use a BDD style approach for building something like this, but as I wasn’t sure how to solve the problem, I spiked out the approach, then added the tests and refined the implementation as required.

Part of spiking out the solution was figuring out how to test it and this initially tripped me up as I was considering mocking IDbCommand and IDataReader ( which gets ugly fast ).

After a little bit of thought, I figured I could use a SqlCommand for the IDbCommand. The following snippet shows what you need to do to use a SqlCommand for testing your NullSafeSet ( note where parameter index comes from ).

_command = new SqlCommand();
_parameterIndex = _command.Parameters.Add( _command.CreateParameter() );

// ... this is then used in the NullSafeSet test ...
_userType.NullSafeSet( _command, null, _parameterIndex );

To find an acceptable IDataReader stand-in, I went poking around with Reflector and found an IDataReader implementation that wraps a DataTable. This gave me a means of quickly and easily controlling the data being passed to the code under test. The following snippet shows how you can set up an IDataReader from a DataTable in as little as 5 lines of code for testing your NullSafeGet ( note the data reader must be “initialised” by calling Read() before using it ):

var dataTable = new DataTable();
dataTable.Columns.Add( ColumnName, typeof( int ) );
dataTable.LoadDataRow( new object[] {TimezoneOffset * 60}, true );
_dataReader = new DataTableReader( dataTable );
_dataReader.Read();

// ... this is then used in the NullSafeSet test ...
_timezoneInfo = (TimeZoneInfo) _userType.NullSafeGet( _dataReader, new[] {ColumnName}, null );

The full source for the TimeZoneInfoUserType and the associated tests is shown below:

The Implementation

    public class TimezoneInfoUserType : IUserType
    {
        public new bool Equals(object x, object y)
        {
            if( ReferenceEquals( x, y ) )
            {
                return true;
            }
            if( x == null || y == null )
            {
                return false;
            }

            return x.Equals( y );
        }

        public int GetHashCode( object x )
        {
            return x.GetHashCode();
        }

        public object NullSafeGet( IDataReader rs, string[] names, object owner )
        {
            var rawValue = NHibernateUtil.Int32.NullSafeGet( rs, names[ 0 ] );
            if( rawValue == null )
            {
                return null;
            }
            var timezoneOffsetMinutes = (int) rawValue;
            var timezoneOffset = new TimeSpan( 0, timezoneOffsetMinutes, 0 );
            return TimeZoneInfoFactory.Create( timezoneOffset.TotalHours );
        }

        public void NullSafeSet( IDbCommand cmd, object value, int index )
        {
            if(value == null)
            {
                NHibernateUtil.Int32.NullSafeSet( cmd, null, index );
                return;
            }
            var timezoneInfo = (TimeZoneInfo) value;
            var offsetInMinutes = timezoneInfo.BaseUtcOffset.TotalMinutes;
            NHibernateUtil.Int32.NullSafeSet( cmd, Convert.ToInt32( offsetInMinutes ) , index );
        }

        public object DeepCopy( object value )
        {
            return value;
        }

        public object Replace( object original, object target, object owner )
        {
            return original;
        }

        public object Assemble( object cached, object owner )
        {
            return cached;
        }

        public object Disassemble( object value )
        {
            return value;
        }

        public SqlType[] SqlTypes
        {
            get { return new[] {SqlTypeFactory.Int32}; }
        }

        public Type ReturnedType
        {
            get { return typeof( TimeZoneInfo ); }
        }

        public bool IsMutable
        {
            get { return false; }
        }
    }

The Tests
Note: If you are wondering about the =()=> sequence, I use MSpec for my testing.

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_saving_a_null_TimezoneInfo
    {
        static int _parameterIndex;
        static TimezoneInfoUserType _userType;
        static IDbCommand _command;

        Establish context = () =>
        {
            _userType = new TimezoneInfoUserType();
            _command = new SqlCommand();
            _parameterIndex = _command.Parameters.Add( _command.CreateParameter() );
        };

        Because of = () =>
            _userType.NullSafeSet( _command, null, _parameterIndex );

        It the_offset_is_stored_as_null = () =>
            ((IDataParameter)_command.Parameters[ _parameterIndex ]).Value.ShouldEqual(DBNull.Value);
    }

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_saving_a_TimezoneInfo_with_a_zero_offset
    {
        static int _parameterIndex;
        static TimezoneInfoUserType _userType;
        static IDbCommand _command;
        static TimeZoneInfo _timezoneInfo;

        Establish context = () =>
        {
            _timezoneInfo = TimeZoneInfoFactory.Create( 0 );
            _userType = new TimezoneInfoUserType();
            _command = new SqlCommand();
            _parameterIndex = _command.Parameters.Add( _command.CreateParameter() );
        };

        Because of = () =>
            _userType.NullSafeSet(_command, _timezoneInfo, _parameterIndex);

        It the_offset_is_stored_as_zero = () =>
            ( (IDataParameter) _command.Parameters[ _parameterIndex ] ).Value.ShouldEqual( 0 );
    }

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_saving_a_TimezoneInfo_with_a_positive_offset
    {
        const int TimezoneOffset = 12;
        static int _parameterIndex;
        static TimezoneInfoUserType _userType;
        static IDbCommand _command;
        static TimeZoneInfo _timezoneInfo;

        Establish context = () =>
        {
            _timezoneInfo = TimeZoneInfoFactory.Create( TimezoneOffset );
            _userType = new TimezoneInfoUserType();
            _command = new SqlCommand();
            _parameterIndex = _command.Parameters.Add( _command.CreateParameter() );
        };

        Because of = () =>
            _userType.NullSafeSet( _command, _timezoneInfo, _parameterIndex );

        It the_offset_is_stored_in_minutes = () =>
            ( (IDataParameter) _command.Parameters[ _parameterIndex ] ).Value.ShouldEqual( TimezoneOffset * 60 );
    }

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_saving_a_TimezoneInfo_with_a_negative_offset
    {
        const int TimezoneOffset = -12;
        static int _parameterIndex;
        static TimezoneInfoUserType _userType;
        static IDbCommand _command;
        static TimeZoneInfo _timezoneInfo;

        Establish context = () =>
        {
            _timezoneInfo = TimeZoneInfoFactory.Create( TimezoneOffset );
            _userType = new TimezoneInfoUserType();
            _command = new SqlCommand();
            _parameterIndex = _command.Parameters.Add( _command.CreateParameter() );
        };

        Because of = () =>
            _userType.NullSafeSet( _command, _timezoneInfo, _parameterIndex );

        It the_offset_is_stored_in_minutes = () =>
            ( (IDataParameter) _command.Parameters[ _parameterIndex ] ).Value.ShouldEqual( TimezoneOffset * 60 );
    }

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_retrieving_a_null_TimezoneInfo
    {
        const string ColumnName = "Timezone";
        static TimeZoneInfo _timezoneInfo;
        static TimezoneInfoUserType _userType;
        static IDataReader _dataReader;

        Establish context = () =>
        {
            _userType = new TimezoneInfoUserType();
            var dataTable = new DataTable();
            dataTable.Columns.Add( ColumnName, typeof( int ) );
            dataTable.LoadDataRow( new object[] {null}, true );
            _dataReader = new DataTableReader( dataTable );
            _dataReader.Read();
        };

        Because of = () =>
            _timezoneInfo = (TimeZoneInfo) _userType.NullSafeGet( _dataReader, new[] {ColumnName}, null );

        It returns_null = () =>
            _timezoneInfo.ShouldBeNull();
    }

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_retrieving_a_TimezoneInfo_with_a_zero_offset
    {
        const string ColumnName = "Timezone";
        static TimeZoneInfo _timezoneInfo;
        static TimezoneInfoUserType _userType;
        static IDataReader _dataReader;

        Establish context = () =>
        {
            _userType = new TimezoneInfoUserType();
            var dataTable = new DataTable();
            dataTable.Columns.Add( ColumnName, typeof( int ) );
            dataTable.LoadDataRow( new object[] {0}, true );
            _dataReader = new DataTableReader( dataTable );
            _dataReader.Read();
        };

        Because of = () =>
            _timezoneInfo = (TimeZoneInfo) _userType.NullSafeGet( _dataReader, new[] {ColumnName}, null );

        It the_BaseUtcOffset_is_zero = () =>
            _timezoneInfo.BaseUtcOffset.TotalHours.ShouldEqual( 0d );

        It the_displayname_is_GMT = () =>
            _timezoneInfo.DisplayName.ShouldEqual( "GMT" );
    }

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_retrieving_a_TimezoneInfo_with_a_positive_offset
    {
        const string ColumnName = "Timezone";
        const int TimezoneOffset = 12;
        static TimeZoneInfo _timezoneInfo;
        static TimezoneInfoUserType _userType;
        static IDataReader _dataReader;

        Establish context = () =>
        {
        _userType = new TimezoneInfoUserType();
        var dataTable = new DataTable();
        dataTable.Columns.Add(ColumnName, typeof(int));
        dataTable.LoadDataRow(new object[] { TimezoneOffset * 60 }, true);
        _dataReader = new DataTableReader(dataTable);
        _dataReader.Read();
        };

        Because of = () =>
            _timezoneInfo = (TimeZoneInfo)_userType.NullSafeGet(_dataReader, new[] { ColumnName }, null);

        It the_BaseUtcOffset_is_generated_from_the_number_of_minutes_retrieved = () =>
            _timezoneInfo.BaseUtcOffset.TotalHours.ShouldEqual( TimezoneOffset );

        It the_displayname_follows_the_pattern_GMT_plus_timezone_offset_ = () =>
            _timezoneInfo.DisplayName.ShouldEqual( "GMT+" + TimezoneOffset );
    }

    [Subject(typeof(TimezoneInfoUserType))]
    public class When_retrieving_a_TimezoneInfo_with_a_negative_offset
    {
        const string ColumnName = "Timezone";
        const int TimezoneOffset = -12;
        static TimeZoneInfo _timezoneInfo;
        static TimezoneInfoUserType _userType;
        static IDataReader _dataReader;

        Establish context = () =>
        {
            _userType = new TimezoneInfoUserType();
            var dataTable = new DataTable();
            dataTable.Columns.Add( ColumnName, typeof( int ) );
            dataTable.LoadDataRow( new object[] {TimezoneOffset * 60}, true );
            _dataReader = new DataTableReader( dataTable );
            _dataReader.Read();
        };

        Because of = () =>
            _timezoneInfo = (TimeZoneInfo) _userType.NullSafeGet( _dataReader, new[] {ColumnName}, null );

        It the_BaseUtcOffset_is_generated_from_the_number_of_minutes_retrieved = () =>
            _timezoneInfo.BaseUtcOffset.TotalHours.ShouldEqual( TimezoneOffset );

        It the_displayname_follows_the_pattern_GMT_minus_timezone_offset_ = () =>
            _timezoneInfo.DisplayName.ShouldEqual( "GMT" + TimezoneOffset );
    }

Categories: NHibernate, TDD, Unit Testing

Yet another BDD extension for xUnit

June 22, 2009 Leave a comment

I’ve been “test infected” for a while and while I still lack the hardcore discipline to apply TDD to everything I do, I have managed to use it through a couple of projects I have worked on.  One of the things I noticed as I was writing more and more tests is that I went from testing a scenario per method (with lots of test methods per class) to testing a scenario per class.  Looking around the web, I think a lot of people go through this evolution toward BDD style testing where you set up a context, apply some action to it and then veryify the results are what you expected.

For my next project, I wanted to formalise this style of testing and make it as easy as possible to do, to that end I did a bunch of research, selected xUnit as my testing framework and then stole incorporated ideas from all over the place to come up with something that allowed me to write tests in the following style:

    public class When_popping_an_empty_operand_stack
    {
        private OperandStack _operandStack;
        private long _value;

        public Action Given()
        {
            return () => { _operandStack = new OperandStack(); };
        }

        public Action When()
        {
            return () => _value = _operandStack.Pop();
        }

        [Then]
        public void the_value_should_be_zero()
        {
            Assert.Equal( 0L, _value );
        }
    }

Note: This is where I ended up after numerous revisions determining what I could and could not do while trying to keep the noise and ceremony to a minimum.

This approach treats the test class as the context (or world) that you set up using the Given delegate, alter using the When delegate and verify the outcomes in the methods marked with the [Then] attribute.  If you’ve seen MSpec then you are probably thinking “Hey, thats just like MSpec… but not quite” and you’d be dead right.  Of all the approaches and syntaxes I saw, MSpec had the best and it was the one that came the closest to doing what I wanted, apart from one (not so minor) issue – the R# test runner does not play nice with MSpec.

To achieve the above test using xUnit was relatively simple and borrowed heavily from this posting by Frederik Kalseth.  The reason I didn’t simply use his version was the requirement to inherit from a common base class.

A final hat tip must be given to Phil Haack as it was his post on Streamlined BDD Using SubSpec for xUnit.NET that set me down the path of playing with xUnit to make it test the way I wanted to.

Going forward, I’ve been looking at xUnits IUseFixture interface and wondering about using Fixtures as a means for encapsulating context where you want to share it across multiple test classes… but I’ll solve that when I get around to needing it.  Code for making xUnit behave as shown in the code above is below. Feedback on both the test approach (Given / When / Then) and the implementation of the xUnit extensions is most welcome.

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public class ThenAttribute : FactAttribute
    {
        protected override IEnumerable<ITestCommand> EnumerateTestCommands(MethodInfo method)
        {
            foreach( var testCommand in base.EnumerateTestCommands( method ) )
            {
                yield return new ThenCommand( testCommand );
            }
        }
    }

    public class ThenCommand : ITestCommand
    {
        private readonly ITestCommand _innerCommand;

        public ThenCommand( ITestCommand innerCommand )
        {
            _innerCommand = innerCommand;
        }

        public MethodResult Execute( object testClass )
        {
            var given = GetGivenAction( testClass );
            EnsureGivenIsValid( given );
            var when = GetWhenAction( testClass );
            EnsureWhenIsValid( when );
            given();
            when();
            return _innerCommand.Execute( testClass );
        }

        public XmlNode ToStartXml()
        {
            return _innerCommand.ToStartXml();
        }

        public string DisplayName
        {
            get { return _innerCommand.DisplayName; }
        }

        public bool ShouldCreateInstance
        {
            get { return _innerCommand.ShouldCreateInstance; }
        }

        private Action GetGivenAction( object testClass )
        {
            return GetAction( testClass, "Given" );
        }

        private void EnsureGivenIsValid( Action given )
        {
            if (given == null)
            {
                throw new ArgumentException( "Test class does not contain a Given() method "
                + "or the Given() method does not return an Action delegate.  "
                + "Please ensure the test class implements a public method with the following signature: "
                + "Action Given()");
            }
        }

        private Action GetWhenAction( object testClass )
        {
            return GetAction( testClass, "When" );
        }

        private void EnsureWhenIsValid(Action when)
        {
            if (when == null)
            {
                throw new ArgumentException("Test class does not contain a When() method "
                + "or the When() method does not return an Action delegate.  "
                + "Please ensure the test class implements a public method with the following signature: "
                + "Action When()");
            }
        }

        private Action GetAction(object testClass, string methodName)
        {
            var wrappedTestClass = Reflector.Wrap(testClass.GetType());
            var givenMethod = wrappedTestClass.GetMethod( methodName );
            if (givenMethod == null)
            {
                return null;
            }
            if(givenMethod.ReturnType != typeof(Action).FullName)
            {
                return null;
            }

            return (Action)givenMethod.MethodInfo.Invoke( testClass, null );
        }
    }

Categories: c#, TDD, Unit Testing

Avoiding the D’oh (aka assumptions are bad)

April 21, 2008 Leave a comment

I’ve just had one of those d’oh moments, if only I had written a unit test for 1 simple constructor I would have saved myself about 3 hours of pointless frustration.

I am currently integrating NHibernate into my current project, which basically means I am writing a lot of mapping files. Having never used NHibernate before I don’t have that invaluable store of wisdom built up from previous implementations – so I am relying on some very simple unit tests to make sure my mapping files are correct.

The problem came about when I was trying to map a collection of value types (EmploymentDetails) that hold the details of the entities (Employee) employment history over time (essentially what store they worked in when).  While I was looking at the domain object I realised I could clean it up a little and make it express the intent more correctly as well as a little more efficiently, so I quickly hacked together the changes and carried on building the mapping file – BIG MISTAKE.  No tests were written, it was only a simple rename and move a couple lines of code around in the constructor right?  WRONG.  I forgot 1 crucial line of code and that just cost me about 3 hours chasing what I thought was a problem in the mapping file, when it was actually a problem in the code.

Integrating a new technology (defined as one you have no prior experience with), is a high risk exercise.  The simplest way to reduce risk is to change as little as possible and make sure it works.  Unit testing allows you to follow this cycle easily, but you have to commit to it completely, otherwise the value of your investment is significantly reduced – you get a false sense of security about the state of your code and build on those assumptions.

It’s also wise to remember that no matter what – you are a terrible coder and should therefore check ALL your assumptions that your current problem is built on before trying to solve the problem – sometimes it is incredibly useful to assert these as preconditions for your test – just so you really are sure your assumptions are correct.

Lesson learned, I’m off to retrofit a test or 2 :)

Categories: TDD

TDD and NCover

March 12, 2008 1 comment

I am learning TDD. It’s hard and I often fall back into old habits of writing code before writing tests – particularly when I can “see” the solution (I tend to code from within existing classes out, meaning I still write the code the way I’d like to use it but not from the tests). This leads to patches in the code that are quite often not tested, and not always required. I found the trick to identifying these patches of code (and quite often refactoring them away or simply deleting them), is to use a code coverage tool while running the tests so I can see what isn’t tested.

I use VS2005 Pro and NUnit for my testing needs. I don’t have any budget to go and buy tools so the commercial code coverage tools were out, however I found that you can still get the earlier versions of NCover and NCoverExplorer for free, and right now they do just what I need. Setting them up is easy:

  1. Make sure you have NUnit installed, setup and running from the console – I am using 2.4.6 for .NET 2.0
  2. Get the last free version of NCover and install it – I am using 1.5.8 beta
  3. Add the locations of nunit-console.exe and ncover.console.exe to your PATH environment variable
  4. Grab a copy of NCover Explorer 1.4 from here
  5. Run NCover Explorer, hit Ctrl+N to Run NCover, configure NCover via the NCover tab and the application to profile via the Application to Profile tab (the Application Arguments will be the name of the assembly containing the NUnit tests) and if you want to limit what is profiled, use the options tab to customise your profiling.

Once you have the profile results up, you can navigate the coverage and see where your tests missed things and look at what you need to do to test what is missed. Critically analyse code that has not been tested, particularly where a majority of the code around it has been covered. I found myself refining the untested code based on one of the following questions quite often:

  • Do I really need that guard condition?
  • Is the scenario I have allowed for likely to happen?
  • Should I be handling that case differently from the rest?

Although it is nice to get code coverage up to 95%, I am realistic about what I am testing. It is a (relatively) small system I have previously talked about, and that means a lot of simple property get/set type code and some “untestable” code that integrates it into the underlying web application (primarily in the global.asax and container / component registration). My primary goal for using code coverage was to identify complex and / or significant parts of the system I had written while in a code first groove and get them under test as soon as possible. Now I have NCover up and running, I am trying to make it a habit to run it every couple of hours to catch those places where I have fallen back into old (bad?) habits.

Categories: Code Coverage, NCover, TDD
Follow

Get every new post delivered to your Inbox.