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:
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 & "" & bareName & ".Tests.cs" Dim codeClass As CodeClass = findClass(item.FileCodeModel.CodeElements) Dim namespaceName As String = codeClass.Namespace.FullName System.IO.File.WriteAllText(newItemPath, "" _ & "#if DEBUG" & vbCrLf _ & "using System;" & vbCrLf _ & "using System.Diagnostics;" & vbCrLf _ & "using NUnit.Framework;" & vbCrLf _ & "" & vbCrLf _ & "namespace " & namespaceName & vbCrLf _ & "{" & vbCrLf _ & " [TestFixture]" & vbCrLf _ & " public class " & codeClass.Name & "_Tests" & vbCrLf _ & " {" & vbCrLf _ & " " & vbCrLf _ & " }" & vbCrLf _ & "}" & vbCrLf _ & "#endif" & 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 &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.
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! 🙂
I’d write a small throwaway console app to do it, personally; or maybe even a PowerShell script.