Tests-behind: Tests as Code-Behind Files

One the first issues that you have to deal with when writing unit tests is – where do I put the the tests? Here’s where I prefer to have them: as close to the code that’s being tested as possible, like so:

tests behind 1

The tests class is hooked up to the original source file in the same way that ASP.NET code-behind files are, as a dependent project item.

You can do this by hacking the *csproj file directly using the <DependentUpon> tag, but to automate it I’ve written a Visual Studio macro (works in Visual Studio 2008, should work in Visual Studio 2005) that creates an appropriately-named tests class that can be invoked like so:

Here’s the macro code (copy and paste into a code module in the Visual Studio Macros IDE: Tools > Macros > Macros IDE)

Sub AddTestsFile()
   Dim item As ProjectItem = DTE.SelectedItems.Item(1).ProjectItem
   Dim fileName As String = item.FileNames(1)
   Dim dir As String = System.IO.Path.GetDirectoryName(fileName)
   Dim bareName As String = System.IO.Path.GetFileNameWithoutExtension(fileName)
   Dim newItemPath As String = dir &amp; &quot;&quot; &amp; bareName &amp; &quot;.Tests.cs&quot;

   Dim codeClass As CodeClass = findClass(item.FileCodeModel.CodeElements)
   Dim namespaceName As String = codeClass.Namespace.FullName

   System.IO.File.WriteAllText(newItemPath, &quot;&quot; _
     &amp; &quot;#if DEBUG&quot; &amp; vbCrLf _
     &amp; &quot;using System;&quot; &amp; vbCrLf _
     &amp; &quot;using System.Diagnostics;&quot; &amp; vbCrLf _
     &amp; &quot;using NUnit.Framework;&quot; &amp; vbCrLf _
     &amp; &quot;&quot; &amp; vbCrLf _
     &amp; &quot;namespace &quot; &amp; namespaceName &amp; vbCrLf _
     &amp; &quot;{&quot; &amp; vbCrLf _
     &amp; &quot;	[TestFixture]&quot; &amp; vbCrLf _
     &amp; &quot;	public class &quot; &amp; codeClass.Name &amp; &quot;_Tests&quot; &amp; vbCrLf _
     &amp; &quot;	{&quot; &amp; vbCrLf _
     &amp; &quot;		&quot; &amp; vbCrLf _
     &amp; &quot;	}&quot; &amp; vbCrLf _
     &amp; &quot;}&quot; &amp; vbCrLf _
     &amp; &quot;#endif&quot; &amp; vbCrLf _
    )

   ' Add as sub-item and show
   Dim newItem As ProjectItem = item.ProjectItems.AddFromFile(newItemPath)
   newItem.Open().Activate()

End Sub

' Utility used by AddTestsFile
Function findClass(ByVal items As System.Collections.IEnumerable) As CodeClass
   For Each codeEl As CodeElement In items
      If codeEl.Kind = vsCMElement.vsCMElementClass Then
         Return codeEl
      ElseIf codeEl.Children.Count &amp;gt; 0 Then
         Dim cls As CodeClass = findClass(codeEl.Children)
         If cls IsNot Nothing Then
            Return findClass(codeEl.Children)
         End If
      End If
   Next
   Return Nothing
End Function

The right-click Project Item context menu shortcut can be wired up to the macro with the help of Sara Ford’s tip about customizing Visual Studio context menus.

Update 11 March 2008: Fixed findClass subroutine which resulted in null reference error, it now recurses correctly.