Salient Solutions

wrasslin ones and nones for fun and profit - Sky Sanders' Blog
Get your own ranked flair here
posts - 92, comments - 103, trackbacks - 0

Database Testing: A disposable MS SQL database fixture

 

These classes build a non-denominational fixture that will Drop-Create-Drop a database in MS SqlServer. Following are sample implementations using NUnit and Fluent NHibernate.

To use the fixture, just supply a connection string that points to the database you want to create, override TestFixtureSetup and add some DDL script to the Scripts StringCollection. 

NOTE: the fixture takes care of dropping and creating and declaring USING so to prepare a standard DB create script:

  1. remove drop/create statements.
  2. remove all USING statements.

The latest source and tests are at http://spikes.codeplex.com/SourceControl/changeset/view/41478

 

In use with NUnit

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using DeadSimpleDTO;
using Loader.Northwind;
using NUnit.Framework;

namespace Tests
{
    [TestFixture]
    public class LoaderFixture : NUnitDbFixture
    {
        private readonly DeadSimpleLoader _loader;

        public LoaderFixture()
        {
            _loader = new DeadSimpleLoader(ConnectionString);
            Scripts.Add(File.ReadAllText(Path.Combine("scripts", "NorthwindExemplar.sql")));
        }

        [Test]
        public void Get()
        {
            var cat = _loader.Get(1);

            Assert.IsNotNull(cat);
            Assert.AreEqual(1, cat.CategoryID);
        }

    }
}

The classes

// <copyright project="Salient.SqlServer" file="DatabaseFixture.cs" company="Sky Sanders">
// This source is a Public Domain Dedication.
// Please see http://spikes.codeplex.com/ for details.   
// Attribution is appreciated
// </copyright> 
// <version>1.0</version>
using System;
using System.Collections.Specialized;
using Salient.SqlServer.Management;

namespace Salient.SqlServer.Testing
{
    public abstract class DatabaseFixture : Database
    {
        private readonly StringCollection _scripts = new StringCollection();
        private int _uuid;


        protected DatabaseFixture(string dataSource, string initialCatalog)
            : base(dataSource, initialCatalog)
        {
        }

        protected DatabaseFixture(string dataSource, string initialCatalog, string userId, string password)
            : base(dataSource, initialCatalog, userId, password)
        {
        }

        protected DatabaseFixture(string connectionString)
            : base(connectionString)
        {
        }

        protected StringCollection Scripts
        {
            get { return _scripts; }
        }

        protected int NextId()
        {
            return ++_uuid;
        }

        protected static int TI()
        {
            return Environment.TickCount;
        }

        protected static decimal TD()
        {
            return Environment.TickCount;
        }

        protected static string TS()
        {
            return Environment.TickCount.ToString();
        }

        public virtual void TestFixtureSetUp()
        {
            DropAndCreate(_scripts);
        }


        public virtual void TestFixtureTearDown()
        {
            Drop();
        }
    }
}

// <copyright project="Salient.SqlServer" file="Database.cs" company="Sky Sanders">
// This source is a Public Domain Dedication.
// Please see http://spikes.codeplex.com/ for details.   
// Attribution is appreciated
// </copyright> 
// <version>1.0</version>
using System.Collections.Specialized;
using System.Data.SqlClient;

namespace Salient.SqlServer.Management
{
    public class Database : SqlCmdRunner
    {
        private const string CreateDb =
            @"
  USE [master]
  GO
  
  CREATE DATABASE [{0}]
  IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
  begin
  	EXEC [{0}].[dbo].[sp_fulltext_database] @action = 'enable'
  end
  ALTER DATABASE [{0}] SET ANSI_NULL_DEFAULT OFF 
  ALTER DATABASE [{0}] SET ANSI_NULLS OFF 
  ALTER DATABASE [{0}] SET ANSI_PADDING OFF 
  ALTER DATABASE [{0}] SET ANSI_WARNINGS OFF 
  ALTER DATABASE [{0}] SET ARITHABORT OFF 
  ALTER DATABASE [{0}] SET AUTO_CLOSE OFF 
  ALTER DATABASE [{0}] SET AUTO_CREATE_STATISTICS ON 
  ALTER DATABASE [{0}] SET AUTO_SHRINK OFF 
  ALTER DATABASE [{0}] SET AUTO_UPDATE_STATISTICS ON 
  ALTER DATABASE [{0}] SET CURSOR_CLOSE_ON_COMMIT OFF 
  ALTER DATABASE [{0}] SET CURSOR_DEFAULT  GLOBAL 
  ALTER DATABASE [{0}] SET CONCAT_NULL_YIELDS_NULL OFF 
  ALTER DATABASE [{0}] SET NUMERIC_ROUNDABORT OFF 
  ALTER DATABASE [{0}] SET QUOTED_IDENTIFIER OFF 
  ALTER DATABASE [{0}] SET RECURSIVE_TRIGGERS OFF 
  ALTER DATABASE [{0}] SET  DISABLE_BROKER 
  ALTER DATABASE [{0}] SET AUTO_UPDATE_STATISTICS_ASYNC OFF 
  ALTER DATABASE [{0}] SET DATE_CORRELATION_OPTIMIZATION OFF 
  ALTER DATABASE [{0}] SET TRUSTWORTHY OFF 
  ALTER DATABASE [{0}] SET ALLOW_SNAPSHOT_ISOLATION OFF 
  ALTER DATABASE [{0}] SET PARAMETERIZATION SIMPLE 
  ALTER DATABASE [{0}] SET READ_COMMITTED_SNAPSHOT OFF 
  ALTER DATABASE [{0}] SET  READ_WRITE 
  ALTER DATABASE [{0}] SET RECOVERY FULL 
  ALTER DATABASE [{0}] SET  MULTI_USER 
  ALTER DATABASE [{0}] SET PAGE_VERIFY CHECKSUM  
  ALTER DATABASE [{0}] SET DB_CHAINING OFF
  
  PRINT('Database [{0}] created...')
  
  GO
  ";

        private const string DropDb =
            @"
USE [master]
GO

IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = '{0}') 
begin
	ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE
	DROP DATABASE {0}
	PRINT('Database [{0}] dropped for recreation...')
end

GO
";

        private const string UseDb = "USE {0}\r\nGO";


        public Database(string dataSource, string initialCatalog)
        {
            DataSource = dataSource;
            InitialCatalog = initialCatalog;
            IntegratedSecurity = true;
        }

        public Database(string dataSource, string initialCatalog, string userId, string password)
        {
            DataSource = dataSource;
            InitialCatalog = initialCatalog;
            UserID = userId;
            Password = password;
        }

        public Database(string connectionString)
        {
            ConnectionString = connectionString;
        }

        public void DropAndCreate()
        {
            DropAndCreate(new StringCollection());
        }

        public void DropAndCreate(StringCollection scripts)
        {
            Drop();
            Create(scripts);
        }

        public void Drop()
        {
            var query = new StringCollection
                            {
                                string.Format(DropDb,
                                              InitialCatalog)
                            };

            var runner = new SqlCmdRunner
                             {
                                 ConnectionString =
                                     new SqlConnectionStringBuilder(ConnectionString) {InitialCatalog = "Master"}.
                                     ConnectionString
                             };

            runner.ExecuteScript(query);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="scripts">Batches to run after database creation. Do NOT include CREATE DATABASE batch. It is included by this method.</param>
        public void Create(StringCollection scripts)
        {
            scripts.Insert(0,
                           string.Format(UseDb,
                                         InitialCatalog));
            scripts.Insert(0,
                           string.Format(CreateDb,
                                         InitialCatalog));

            var runner = new SqlCmdRunner
                             {
                                 ConnectionString =
                                     new SqlConnectionStringBuilder(ConnectionString) {InitialCatalog = "Master"}.
                                     ConnectionString
                             };
            runner.ExecuteScript(scripts);
        }

        public void Create()
        {
            Create(new StringCollection());
        }
    }
}

// <copyright project="Salient.SqlServer" file="SqlCmdRunner.cs" company="Sky Sanders">
// This source is a Public Domain Dedication.
// Please see http://spikes.codeplex.com/ for details.   
// Attribution is appreciated
// </copyright> 
// <version>1.0</version>
using System.Collections.Specialized;
using System.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;

namespace Salient.SqlServer.Management
{
    /// <summary>
    /// Starting on a new DB fixture
    /// </summary>
    public class SqlCmdRunner
    {
        private SqlConnectionStringBuilder _csBuilder = new SqlConnectionStringBuilder();

        #region Connection String Properties

        public string InitialCatalog
        {
            get { return _csBuilder.InitialCatalog; }
            set { _csBuilder.InitialCatalog = value; }
        }

        public string DataSource
        {
            get { return _csBuilder.DataSource; }
            set { _csBuilder.DataSource = value; }
        }

        public string ConnectionString
        {
            get { return _csBuilder.ConnectionString; }
            set { _csBuilder = new SqlConnectionStringBuilder(value); }
        }

        public bool IntegratedSecurity
        {
            get { return _csBuilder.IntegratedSecurity; }
            set { _csBuilder.IntegratedSecurity = value; }
        }

        public string UserID
        {
            get { return _csBuilder.UserID; }
            set { _csBuilder.UserID = value; }
        }

        public string Password
        {
            get { return _csBuilder.Password; }
            set { _csBuilder.Password = value; }
        }

        #endregion

        public int[] ExecuteScript(StringCollection script)
        {
            return ExecuteScript(script, ExecutionTypes.Default);
        }

        public int[] ExecuteScript(StringCollection script, ExecutionTypes executionTypes)
        {
            using (var conn = new SqlConnection(ConnectionString))
            {
                var serverConn = new ServerConnection(conn);
                var server = new Server(serverConn);

                // ExecutionFailureException

                int[] result;
                try
                {
                    result = server.ConnectionContext.ExecuteNonQuery(script, executionTypes);
                }
                catch (ExecutionFailureException ex)
                {
                    throw ex.InnerException;
                }
                return result;
            }
        }
    }
}

using System.Configuration;
using System.Text;
using NUnit.Framework;
using Salient.SqlServer.Testing;

namespace Tests
{
    public class NUnitDbFixture : DatabaseFixture
    {
        public NUnitDbFixture()
            : base(ConfigurationManager.ConnectionStrings["testDb"].ConnectionString)
        {
        }

        /// <summary>
        /// Empty override as a place to hang the attribute
        /// </summary>
        [TestFixtureSetUp]
        public override void TestFixtureSetUp()
        {
            base.TestFixtureSetUp();
        }

        /// <summary>
        /// Empty override as a place to hang the attribute
        /// </summary>
        [TestFixtureTearDown]
        public override void TestFixtureTearDown()
        {
            base.TestFixtureTearDown();
        }
    }
}




Technorati tags: ,

Print | posted on Monday, February 15, 2010 6:00 AM |

Feedback

Gravatar

# re: Database Testing: A disposable MS SQL database fixture

Now go back to database you have created to restore the database and verify the LogicalName. By default SQL Server name it like “DatabaseName” (here in our case Test) for file type “Data” and “DatabaseName_Log” for file type “Log”.
8/19/2010 11:07 PM | online roulette

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 4 and 3 and type the answer here:

Powered by: