May 2 2007

How to make unit tests transactional in TFS

Category: Selfish MotivationJoeGeeky @ 17:01

At some point, all good developers make the realization that Unit Testing is not evil and is the best way to ensure your code works as you expect it to. With that said, the moment you start writing tests for methods that interact with a database you start to run into some really unique problems that can lead to a lot of unneeded code. 

Generally speaking you always want to leave the system as you found it when testing started. That means that any data you add/edit/delete during the test needs to be restored when the test has concluded.  For the longest time I would spend my time writing test rigs to manage all these functions for each data method being tested.  It didn't take me long to realize that this was a waste of time and that there had to be a way to make this easier.

With that in mind  I set out to make my unit tests transactional since transactions have rollback support. Stealing a trick from the COM world I was able to find a way to make my test transactional. Here is what you need to do. As I go though this, keep in mind that I am using TFS Unit Tests run against a Microsoft SQL Server. This same technique can be used with other technologies (Ex. NUnit) although the syntax will be different.

One last note... If you look at the line "config.TransactionTimeout = 900", this may seem like a really long time. This is set to this length to ensure you have plenty of time to step through code during tests. Adjust this as needed.

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System
Imports System.Configuration
Imports System.EnterpriseServices

''' <summary>
''' Abstract class defining a transactional baseline for database driven unit tests
''' </summary>
<TestClass()> _
Public MustInherit Class DatabaseTransactionTestClass
    ''' <summary>
    ''' Hold a defined in progress transaction
    ''' </summary>
    Private TestTransactionScope As TransactionScope
    ''' <summary>
    ''' Init method to setup transaction requirements
    ''' </summary>
    <ClassInitialize()> _
    Public Shared Sub TransactionSetup(ByVal testContext As TestContext)
        Dim config As ServiceConfig = New ServiceConfig()
        config.Transaction = TransactionOption.RequiresNew
        config.TransactionTimeout = 900
        config.IsolationLevel = TransactionIsolationLevel.RepeatableRead
        ServiceDomain.Enter(config)
    End Sub
    ''' <summary>
    ''' Cleanup method to roll-back all database changes
    ''' </summary>
    <ClassCleanup()> _
    Public Shared Sub TransactionTearDown()
        RollbackTransaction()
        ServiceDomain.Leave()
    End Sub
    ''' <summary>
    ''' Init method to define the start of a transaction on a per-test basis 
    ''' </summary>
    <TestInitialize()> _
    Public Sub TestSetup()
        TestTransactionScope = New TransactionScope
    End Sub
    ''' <summary>
    ''' Cleanup method to close and abort the transaction
    ''' </summary>
    <TestCleanup()> _
    Public Sub TestCleanup()
        If Not TestTransactionScope Is Nothing Then
            TestTransactionScope.Dispose()
        End If
    End Sub
    ''' <summary>
    ''' Rolls back and existing test level transactions as defined by the TestTransactionScope
    ''' </summary>
    Public Shared Sub RollbackTransaction()
        If ContextUtil.IsInTransaction Then
            ContextUtil.SetAbort()
        End If
    End Sub
End Class

Tags: ,

Comments

1.
Claudia Claudia United States says:

Wonderful illustrated information. I thank you about that. No doubt it will be very useful for my future projects. Would like to see some other posts on the same subject!

2.
Jada Jada United States says:

You made tremendous great ideas there. I made a search on the topic and noticed almost all peoples will agree with your blog.

Comments are closed