Thursday, July 23, 2009

Create a Test Dataset for Unit Testing

Here is a way to set up a test dataset for unit testing using an embedded text file as the test data source, so that the unit tests are not made brittle by having to rely on a database connection.

First I added a new folder TestData to my UnitTest project.  In that folder, I created a tab-delimited TestDataset.txt file with a header row, then I set the Properties for TestDataset.txt so that Build Action = Embedded Resource.

Then I created an internal class TestDataset.cs in my UnitTest project to read the TestDataset.txt file.  The class constructor reads the text file into a private List<string[]>, which can then be used to populate BEL objects.  This class can be instantiated in the NUnit [TestFixtureSetUp] method. Here is the code:

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Reflection;
  4: using System.IO;
  5: using System.Collections.ObjectModel;
  6: using Rhino.Mocks;
  7: 
  8: namespace TNC.ConProDataSync.Synchronize.UnitTests.TestData
  9: {
 10:     /// <summary>
 11:     /// Generates a test dataset.  Instantiate in a TestFixtureSetUp. 
 12:     /// </summary>
 13:     internal class TestDataset
 14:     {
 15:         private List<string[]> _testDataset;
 16:         
 17:         /// <summary>
 18:         /// Constructor reads the test dataset from the embedded resource text file
 19:         /// </summary>
 20:         internal TestDataset()
 21:         {
 22:             _testDataset = GetTestDataset(Assembly.GetExecutingAssembly());
 23:         }
 24: 
 25:         /// <summary>
 26:         /// Number of datarows in the test dataset
 27:         /// </summary>
 28:         internal int NumberTestRecords { get { return _testDataset.Count; } }
 29: 
 30:         /// <summary>
 31:         /// Test dataset of populated BEL objects
 32:         /// </summary>
 33:         internal Collection<MyBel> TestDataset()
 34:         {
 35:             Collection<MyBel> dataset = new Collection<MyBel>();
 36:             foreach (string[] datarow in _testDataset)
 37:             {
 38:                 MyBel bel = MockRepository.GenerateStub<MyBel>();
 39:                 bel.Prop0 = datarow[0];
 40:                 bel.Prop1 = datarow[1];
 41:                 // etc.
 42:                 dataset.Add(bel);
 43:             }
 44:             return dataset;
 45:         }
 46: 
 47:         /// <summary>
 48:         /// Reads the test dataset from the embedded text resource file
 49:         /// </summary>
 50:         /// <param name="asm">Executing assembly</param>
 51:         /// <returns>List of string arrays populated with test data</param>
 52:         private static List<string[]> GetTestDataset(Assembly asm)
 53:         {
 54:             List<string[]> dataset = new List<string[]>();
 55:             int lineCount = 0;
 56:             try
 57:             {
 58:                 // read the datarows from the TestDataset file, skipping the 1st header line
 59:                 using (Stream strm = asm.GetManifestResourceStream(TestDatasetEmbeddedResourceName(asm)))
 60:                 {
 61:                     using (StreamReader reader = new StreamReader(strm))
 62:                     {
 63:                         while (reader.Peek() >= 0)
 64:                         {
 65:                             lineCount++;
 66:                             string dataline = reader.ReadLine();
 67:                             if (lineCount > 1)
 68:                             {
 69:                                 // each line in the text file is tab-delimited
 70:                                 string[] datarow = dataline.Split(char.Parse(char.ConvertFromUtf32(9)));
 71:                                 dataset.Add(datarow);
 72:                             }
 73:                         }
 74:                     }
 75:                 }
 76:             }
 77:             catch { throw; }
 78:             return dataset;
 79:         }
 80: 
 81:         /// <summary>
 82:         /// Fully-qualified name of the embedded test dataset text file
 83:         /// </summary>
 84:         /// <param name="asm">Executing assembly</param>
 85:         private static string TestDatasetEmbeddedResourceName(Assembly asm)
 86:         {
 87:             return asm.GetName().Name + ".TestData.TestDataset.txt";
 88:         }
 89:     }
 90: }

Wednesday, July 15, 2009

Quality Control Presentation

I was asked to put together a presentation on how my developer team can improve our code quality, based on some of the unit testing best practices and techniques I’ve learned recently.  I decided to share my presentation here in case anyone else finds it useful.

Knowledge Sharing – Quality Control

Monday, July 13, 2009

Visual Studio Class Library Configuration for Unit Testing

This is the best practice configuration used by The Nature Conservancy’s TIS Conservation Team for setting up a Visual Studio source code class library and accompanying unit test class library.  The Conservation Team is currently using NUnit 2.5 and Rhino Mocks 3.5.

Here is the physical layout of the folders including the key files that are managed by Subversion:

Folder_Layout

My Unit Test Project

  • Docs:  standard TNC practice to include an INSTALL.txt document with every project inside of the Docs folder.  Contains basic instructions on how to add the unit test class library to a Visual Studio solution.
  • UnitTestCode:  this folder contains all of the unit test C# (or VB) class files, the VS project file (.csproj) and the NUnit GUI Runner project (.nunit and .VisualState.xml) files
  • IGNORES.txt: SubVersion ignore property list
  • Readme.txt:  standard TNC practice to include a Readme file with summary, prerequisite, and other basic information pertaining to the unit test class library

The IGNORES.txt file looks like this:

Bin
bin
Obj
obj
TestResult.xml

My Source Code Project

  • Docs:  standard TNC practice to include an INSTALL.txt document with every project inside of the Docs folder.  Contains basic instructions on how to add the source code class library to a Visual Studio solution.
  • SourceCode:  this folder contains all of the source code C# (or VB) class files and the VS project file (.csproj)
  • IGNORES.txt: SubVersion ignore property list
  • Readme.txt:  standard TNC practice to include a Readme file with summary, prerequisite, and other basic information pertaining to the source code class library
  • version.txt:  standard TNC practice to include a version number

The IGNORES.txt file looks like this:

*.aps
*.bak
*.bpt
*.bro
*.bsc
*.cdb
*.class
*.dep
*.dpd
*.err
*.exe
*.exp
*.gid
*.gz
*.hlp
*.hm
*.idb
*.ilk
*.jar
*.lnk
*.log
*.ncb
*.obj
*.ocx
*.opt
*.pch
*.pdb
*.ph
*.pjt
*.plg
*.res
*.sav
*.sbr
*.scc
*.suo
*.tar
*.tlh
*.tli
*.trg
*.vtg
*.ww
*.zip
Bin
bin
Obj
obj