Test Driven Development - Tools and Techniques

by Call me dave... 17. December 2008 18:26

Earlier tonight Chris and I presented our talk on Test Driven Development.  I have to say I was rather impressed at the turn out since its only a week away from Christmas and we still managed around 40 people.  As promised here are the slides from the presentation and all of the code/files that were used during the talk. 

Test_Infected_Presentation.ppt (544.50 kb) 

SkillsMatter.TestInfected.rar (6.15 mb) 

Running Fitnesse

  1. Uncompress the SkillsMatter.TestInfected.rar file into C:\SkillsMatter\TDDTalk
  2. Run C:\SkillsMatter\TDDTalk\TDD\fitnesse\run.bat - This will start the Fitnesse web server on port 8080
  3. load http://localhost:8080/MidlandsFoodsFirstCut
  4. Press the Suite link and the tests should execute and all fail - This demonstrates the deliverable from a Business Analyst
  5. Load http://localhost:8080/MidlandsFoods 
  6. Press the Suite link and the tests should execute and most will pass
Debugging Fitnesse
  1. Uncompress the SkillsMatter.TestInfected.rar file into C:\SkillsMatter\TDDTalk
  2. Run C:\SkillsMatter\TDDTalk\TDD\fitnesse\run.bat - This will start the Fitnesse web server on port 8080
  3. Load C:\SkillsMatter\TDDTalk\TDD\JackPlaysSnap\JackPlaysSnap.sln
  4. Set the Fitnesse project as the StartUp Project
  5. Under project propertie
    1. Set Start external program to C:\SkillsMatter\TDDTalk\TDD\fitnesse\dotnet\TestRunner.exe
    2. Set the Commandline arguements to localhost 8080 MidlandsFoods.GiantWinsJackNeverSnaps
    3. Set the Working directory to C:\SkillsMatter\TDDTalk\TDD\fitnesse\
  6. Set a breakpoint in JackPlaysSnapFitness.cs
  7. Run the project under debug mode

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Agile | Test Driven Development | Unit tests

Sprint Milestones - A second level of reporting

by Call me dave... 13. November 2008 01:38

One of Scrum's great strengths is that it promotes transparency. 

The daily stand-up ensures that team members understand what their peers are working on.  It also helps to quickly identify cards (tasks) that were underestimated or when a particular team member or pair have become stuck and aren't making progress.  Unfortunately while SCRUM is great at managing the project it can be more difficult to present the progress to senior management.

For example which of the following metrics should be used as the public face of the SCRUM.

  • Sprint burn chart

  • Completed stories

  • Card life cycle (planned, in progress completed)

Originally my team was only using a burn down chart for reporting but in the last couple of sprints we have added the concept of Sprint Milestones.  A Milestone is

  • Scheduled

  • Dependent on a set of cards

  • Completed

Once all of the cards in the Milestone have been complete we move the Milestone card from Scheduled to Complete.  When reporting a Milestones progress we list the number of cards completed over the total number of cards (i.e. 2/6).

 The following is an example of the type of information that is placed in the fortnightly progress report that is presented to my projects steering committee.

 

Sprint: 1
Sprint Goal:  Integration between Eagle and Bloomberg
Sprint duration: (3/11 - 22/11)
Milestones:
Bloomberg market data transformed into Internal schema (3/8) - GREEN
Market data loaded into Eagle (2/5) - GREEN
Bloomberg data exported to down stream systems (0/5) - YELLOW
Sprint complete (5/18) - YELLOW
 Burn down chart:
<image here...>
Issue List:
<table of outstanding issues/external factors that are blocking the team>

 

In many ways you can think of Milestones being an aggregated view of the user stories and tasks within a sprint.  The most important criteria for determining if an item should be wrapped within an Milestone is whether it needs to be publicly reported on or whether it contains integration points with other teams and so needs to be carefully managed.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Agile | Unit tests

Testing Tools: Temporary Sql Express Database

by Call me dave... 24. August 2008 18:30

The Temporary SQL Express database is used to create a database that exists only within the confines of a unit test or test fixture.  I have used the class to test deployment issues with database scripts or when testing complex integration scenarios that can't be placed into a database transaction such as those within SQL Server Integration Services.  

It can be used as follows.  

   1: [Test]
   2: public void TemporaryDatabaseExample()
   3: {
   4:     using (var tempDb = new TemporarySqlExpressDatabase())
   5:     {
   6:         using (var con = new SqlConnection(tempDb.ConnectionString))
   7:         {
   8:             con.Open();
   9:             using (var cmd = new SqlCommand("CREATE TABLE foo (id int)", con))
  10:             {
  11:                 cmd.CommandType = CommandType.Text;
  12:                 cmd.ExecuteNonQuery();
  13:             }
  14:  
  15:             using (var cmd = new SqlCommand("INSERT INTO foo (id) VALUES (1)", con))
  16:             {
  17:                 cmd.CommandType = CommandType.Text;
  18:                 cmd.ExecuteNonQuery();
  19:             }
  20:  
  21:             using (var cmd = new SqlCommand("SELECT id FROM foo", con))
  22:             {
  23:                 cmd.CommandType = CommandType.Text;
  24:                 var result = (int)cmd.ExecuteScalar();
  25:                 Assert.AreEqual(1, result);
  26:             }
  27:         }
  28:     }
  29: }

Under the covers the class uses a TemporaryDirectory which ensures that a data files are deleted after the object is destroyed. 

   1: using System;
   2: using System.Data.SqlClient;
   3: using System.IO;
   4:  
   5: namespace PebbleSteps.TestingTools
   6: {
   7:     public class TemporarySqlExpressDatabase : IDisposable
   8:     {
   9:         private const string SqlExpressConnectionString
  10:             = "Data Source=.\\sqlexpress;Initial Catalog=tempdb;Integrated Security=true;User Instance=True;";
  11:  
  12:         private readonly string _temporaryDatabaseFileName;
  13:         private readonly TemporaryDirectory _temporaryDirectory;
  14:         private bool _disposed;
  15:  
  16:         public TemporarySqlExpressDatabase()
  17:         {
  18:             _temporaryDirectory = new TemporaryDirectory();
  19:             DatabaseName = "temp" + Guid.NewGuid().ToString("N");
  20:             _temporaryDatabaseFileName = Path.Combine(_temporaryDirectory.FullName, DatabaseName + ".mdf");
  21:  
  22:             using (var connection = new SqlConnection(SqlExpressConnectionString))
  23:             {
  24:                 connection.Open();
  25:  
  26:                 using (SqlCommand command = connection.CreateCommand())
  27:                 {
  28:                     command.CommandText =
  29:                         string.Format("CREATE DATABASE {0} ON PRIMARY (NAME={0}, FILENAME='{1}', SIZE = 3000KB) LOG ON ( NAME = N'{0}_Log', FILENAME = N'{1}_Log.ldf', SIZE = 512KB)", DatabaseName, _temporaryDatabaseFileName);
  30:                     command.ExecuteNonQuery();
  31:  
  32:                     command.CommandText = "EXEC sp_detach_db '" + DatabaseName + "', 'true'";
  33:                     command.ExecuteNonQuery();
  34:                 }
  35:             }
  36:         }
  37:  
  38:         public string ConnectionString
  39:         {
  40:             get
  41:             {
  42:                 return
  43:                     string.Format(@"Server=.\SQLExpress;AttachDbFilename={0};Database={1}; Trusted_Connection=Yes;",
  44:                                   _temporaryDatabaseFileName, DatabaseName);
  45:             }
  46:         }
  47:  
  48:         public string DatabaseName { get; private set; }
  49:  
  50:         #region IDisposable Members
  51:  
  52:         public void Dispose()
  53:         {
  54:             Dispose(true);
  55:             GC.SuppressFinalize(this);
  56:         }
  57:  
  58:         #endregion
  59:  
  60:         private static void ClearConnectionsToTemporaryDatabase()
  61:         {
  62:             GC.Collect();
  63:             SqlConnection.ClearAllPools();
  64:         }
  65:  
  66:         private void Dispose(bool disposing)
  67:         {
  68:             if (!_disposed)
  69:             {
  70:                 // Cleanup Managed resources
  71:                 ClearConnectionsToTemporaryDatabase();
  72:                 _temporaryDirectory.Dispose();
  73:  
  74:                 if(disposing)
  75:                 {
  76:                     //Clean unmanaged resources
  77:                 }
  78:  
  79:                 _disposed = true;
  80:             }
  81:         }
  82:  
  83:         ~TemporarySqlExpressDatabase()
  84:         {
  85:             Dispose(false);
  86:         }
  87:     }
  88: }

The TemporaryDirectory class has been updated so that it sleeps for up to 3 seconds while it waits until any running processes to close open file handles on the folder.

   1: using System;
   2: using System.IO;
   3: using System.Security.AccessControl;
   4: using System.Threading;
   5:  
   6: namespace PebbleSteps.TestingTools
   7: {
   8:     /// <summary>
   9:     /// Creates a Temporary Directory that is deleted when the Dispose method is called.  The class can be used
  10:     /// in Unit tests.
  11:     /// </summary>
  12:     public class TemporaryDirectory : IDisposable
  13:     {
  14:         private readonly DirectoryInfo _directory;
  15:         private bool _disposed;
  16:  
  17:         // The class constructor.
  18:         public TemporaryDirectory()
  19:         {
  20:             // Create the temporary directory
  21:             string directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
  22:             _directory = Directory.CreateDirectory(directoryPath);
  23:  
  24:             // Change security to full control so that directory can be used by
  25:             // services under test
  26:             DirectorySecurity dirSec = _directory.GetAccessControl();
  27:             dirSec.AddAccessRule(
  28:                 new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, AccessControlType.Allow));
  29:             _directory.SetAccessControl(dirSec);
  30:         }
  31:  
  32:         /// <summary>
  33:         /// FullName of the folder.  Files created here will be automatically deleted
  34:         /// </summary>
  35:         public string FullName
  36:         {
  37:             get { return _directory.FullName; }
  38:         }
  39:  
  40:         /// <summary>
  41:         /// Persists a stream to a file that is created in the temporary directory
  42:         /// </summary>
  43:         /// <param name="s"></param>
  44:         /// <param name="fileName"></param>
  45:         public void StoreToDirectory(Stream s, string fileName)
  46:         {
  47:             using (var r = new StreamReader(s))
  48:             {
  49:                 using (var w = new StreamWriter(Path.Combine(_directory.Name, Path.Combine(_directory.FullName, fileName))))
  50:                 {
  51:                     w.Write(r.ReadToEnd());
  52:                 }
  53:             }
  54:         }
  55:  
  56:         #region IDisposable
  57:  
  58:         public void Dispose()
  59:         {
  60:             Dispose(true);
  61:             GC.SuppressFinalize(this);
  62:         }
  63:  
  64:         private void Dispose(bool disposing)
  65:         {
  66:             if (!_disposed)
  67:             {
  68:                 // Cleanup Unmanaged resources, wait up to 3 seconds for the test to complete...
  69:                 int retryCount = 3;
  70:                 while (retryCount > 0)
  71:                 {
  72:                     try
  73:                     {
  74:                         Directory.Delete(_directory.FullName, true);
  75:                         retryCount = 0;
  76:                     }
  77:                     catch (IOException)
  78:                     {
  79:                         retryCount--;
  80:                         Thread.Sleep(1000);
  81:                     }
  82:                 }
  83:                 _disposed = true;
  84:             }
  85:         }
  86:  
  87:         ~TemporaryDirectory()
  88:         {
  89:             Dispose(false);
  90:         }
  91:  
  92:         #endregion
  93:     }
  94: }

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Unit tests

Testing Tools: The Temporary Directory

by Call me dave... 24. August 2008 18:07

After moving to England I quickly found that I was pining for a library of test utility classes that a colleague of mine had written while we were both consulting on a large ETL project.  I will be slowly rewriting the classes and placing the code on PebbleSteps.  The first class I will be discussing is the TemporaryDirectory which allows the developer to dump files into a folder which gets automatically deleted when the test completes.

For a quick recap the best unit tests:

  • Are fast
  • Are rerunnable
  • Don't leave a mess

Slow unit tests are inevitably moved into the nightly build process.  This implies that the last two unit test attributes are the most important.  Problems arise when the unit tests need to interact with resources.  Each time the unit test leaves the happy world of burning CPU cycles and moves into interacting with other systems traces of the interaction are left.  Log files are created, alerts are generated, database tables are changed.  Luckily many resources implement the resource manager APIs and all the modifications disappear when the transaction rolls back.  Unfortunately there are a number of scenarios where it is impossible to place the entire test inside a transaction.  

These include:

  • Interacting with files
  • Creating databases
  • Interacting with systems that manage their own transactions such as SQL Server Integration Server 

When testing a component that interacts with files it is important that the unit test cleans up the files after the test completes.  The TemporaryDirectory class automatically creates a directory in its constructor and removes the directory when the Dispose method is called.

        [Test]
        public void Market_data_file_validator()
        {
            const string BloombergSecurityDataResource = "Bloomberg.Security.txt";
            using (TemporaryDirectory tempDir = new TemporaryDirectory())
            {
                tempDir.StoreToDirectory
                    (this.GetType().Assembly. GetManifestResourceStream(BloombergSecurityDataResource), "Security.txt");

                SecurityFileValidator validator = new SecurityFileValidator();
                Assert.IsTrue(validator.Validate(Path.Combine(tempDir.DirectoryPath, "Security.txt"));
            }
        }

Here is the code...  Enjoy!

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.AccessControl;


namespace PebbleSteps.TestingTools
{
    /// <summary>
    /// Creates a Temporary Directory that is deleted when the Dispose method is called.  The class can be used
    /// in Unit tests.
    /// </summary>
    public class TemporaryDirectory : IDisposable
    {
        private bool disposed = false;
        private DirectoryInfo _directory;
       
        /// <summary>
        /// FullName of the folder.  Files created here will be automatically deleted
        /// </summary>
        public string FullName {get {return _directory.FullName;}}
       
        // The class constructor.
        public TemporaryDirectory()
        {
            // Create the temporary directory
            string directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
            _directory = Directory.CreateDirectory(directoryPath);
           
            // Change security to full control so that directory can be used by
            // services under test
            DirectorySecurity dirSec = _directory.GetAccessControl();
            dirSec.AddAccessRule(
                new FileSystemAccessRule("Everyone", FileSystemRights.FullControl,AccessControlType.Allow));
            _directory.SetAccessControl(dirSec);
        }
       
        /// <summary>
        /// Persists a stream to a file that is created in the temporary directory
        /// </summary>
        /// <param name="s"></param>
        /// <param name="fileName"></param>
        public void StoreToDirectory(Stream s, string fileName)
        {
            using (StreamReader r = new StreamReader(s))
            {
                using (StreamWriter w = new StreamWriter(Path.Combine(_directory.Name, Path.Combine(_directory.FullName, fileName))))
                {
                    w.Write(r.ReadToEnd());
                }
            }
        }
       
        #region IDisposable
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if(!this.disposed)
            {
                if(disposing)
                {
                    // Cleanup Managed resources
                }
                // Cleanup Unmanaged resources
                Directory.Delete(_directory.FullName, true);
                disposed = true;
            }
        }

        ~TemporaryDirectory()
        {
            Dispose(false);
        }
        #endregion
    }
}

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Unit tests

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

Who is Dave

David Ross - Gold Coast QLD

Aussie developer in London.

Email:willmation(at)gmail.com