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)

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
   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.

2 thoughts on “Tests-behind: Tests as Code-Behind Files

  1. Great post! I wonder of you’ve encountered the following scenario, or think you might know how to handle it:

    We currently generate a large portion of out data access layer. These partial classes files are currently stored in a “GeneratedSource” folder.

    So the structure looks something like this in the solution explorer:

    – Customer.cs
    – GeneratedSource
    +- Customer.cs

    I am aware of the DependentUpon XML tag in the csproj files, but in order to do this, I’d have to manually edit dozens of project files and rename literally hundreds of code files.

    Do you know a way to automate this process?

    Thanks! 🙂

    1. I’d write a small throwaway console app to do it, personally; or maybe even a PowerShell script.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s