Within the context of testing

This post  is written for .Net, using C#, but the methods in use should be fairly easy to implement in most other computer languages.

 

Background

I’ve often looked for a good way to write reusable testing scenarios that were clean to use, easy to set up and most importantly – easy to read six months afterwards.

Looking at one of Ayende’s blog posts, where he discusses  scenario driven tests, I found something lacking: Whilst he has a great thing going for testing scenarios against a set context, he has limited himself to a very constrained situation, something that I needed to expand to suit my needs. The issue is that having a context that represents “the system” is not enough – I want to be able to specify exactly the state that my system is in when I execute a scenario, with the ambition of being able to re-use the various situations.

 

Situations and Settings

I wanted to be able to change contexts with ease, so that my test scenarios ultimately look something like:

[TestClass]
public class EventMonitorTests : TestBase
{
    [TestMethod]
    public void Given_When_Then( )
    {
        using( DdTestContext testContext = new DdTestContext( true ) )
        {
            // Prepare
            int initialCount = testContext.Clients.TotalResponseCount( );
            PrepareSituation< RegisterThreeClients >( testContext );

            // Invoke                
            ExecuteScenario< RespondToAllClients >( testContext );

            int actualCount = testContext.Clients.TotalResponseCount( );

            // Assert
            actualCount.ShouldBeExactly( initialCount + 3 );
        }
    }
}

 

The idea is to introduce an interface ISituation as well as IScenario that are known to my test base-class. I can then invoke a Situation and Scenario in various combinations, so that I can test behaviors under different circumstances without having to repeat myself.

ISituation has a Prepare statement, while an IAction  has an Execute method, both accepting the testContext object:

public interface ISituation
{
    void Prepare( DdTestContext testContext );
}

public interface IScenario
{
    void Execute( DdTestContext context );
}

I gave it some thought as to whether I needed this separation or not, but concluded that having a clean separation between rigging a situation and executing a scenario better helps the reader to understand the tests.

Additionally, I needed to declare the methods within the base class that are responsible for executing PrepareSituation and ExecuteScenario:

public void PrepareSituation< T >( DdTestContext testContext ) where T : ISituation, new( )
{
    T situation = new T( );
    situation.Prepare( testContext );
}

public void PrepareScenario< T >( DdTestContext testContext ) where T : IScenario, new( )
{
    T scenario = new T( );
    scenario.Execute( testContext );
}

The functions are limited to accepting only objects that honor the respective interfaces.

This takes care of the execution, as you can guess,For the sake of being practical, whenever I am writing a new batch of scenarios that I would like to test, I often end up writing most ISituation implementations within one single Situations.cs file as well as gathering the IScenario implementations in a corresponding Scenarios.cs file. This is purely to keep the aspects together, when they are simple.

 

image

This depends largely on the complexity of the situations and scenarios. For rigging up more complex scenarios, I would normally use a sub-folder within my solition explorer, and name each file after it’s class name, as is normal.

 

The Context class

Initially, I wanted a nice, controlled way to control transactions for my tests, as they often interact with database. The class DdTestContext represents the system under test as a whole, and can thus contain some sort of transaction object that is configured for the database being tested.

 

public class DdTestContext : IDisposable
{
    private Transaction _transaction;

    public DdTestContext( bool useTransaction )
    {
        if( useTransaction )
        {
            _transaction = new Transaction( );
            _transaction.Begin( );
        }
    }

    public void Dispose( )
    {
        if( _transaction == null )
            _transaction.Rollback();
    }
}

Thus, with this construct, I now have a somewhat stable way of executing my tests within a transactional context without too much effort:

[TestMethod]
public void Given_When_Then( )
{
    using( DdTestContext testContext = new DdTestContext( true ) )
    {
        // Prepare

        // Invoke

        // Assert
        Assert.Inconclusive( "Not yet implemented" );
    }
}

This instantly became a code snippet :)

Every test that executes within the using clause will initiate a transaction, and roll it back again once the scope is out (or unhandled exceptions occur). My database is safe, for now.

Whenever I don’t need database support (Unit tests), I can simply opt for false as the constructor argument and no transaction will be initiated for that particular context.

I dragged the using  statement into each test as opposed to initializing the testcontext in a pre/post method in order to control the use of transactions individually for each test.

 

Still to come

  • How I expanded the testcontext with useful functions
  • Using extension methods extensively

Comments are very welcome :)

Leave a Reply