You are currently browsing the monthly archive for June 2008.

In my attempts to remove even more friction from writing tests I have a new VS macro for running the tests.

I use ReSharper’s unit test runner as it significantly reduces context switch from writing the code to running it. Besides, it looks so damn sexy!! It has a Unit Test Explorer which shows you all the tests in the solution, and then a Unit Test Session where you can select groups of tests to run (and it’s tabbed so you can have different groups of tests). I use the Unit Test Session to isolate just the tests relevant to the code I’m working on.

With the previously posted BDD macro and R# templates, I get smooth flowing writing of tests but then I find myself moving to the mouse to run the tests. What I wanted to do was use shortcuts.

There is an existing shortcut in R# for showing the Unit Test Session (Ctrl+Alt+R). You can then set another shortcut for the command ReSharper_UnitTestSession_RunAll. I set Ctrl+T as the shortcut to this command.

With these 2 in place, it is reasonably quick to write test, hit Ctrl+Alt+R, Ctrl+T and see the selected tests run (including the newly written test). This is reasonably smooth.

But what I want to do is get it down to one shortcut and ideally switch focus back to the text editor so I can carry on writing my tests. Well, a quick macro bound to the shortcut Ctrl+T and I am partway there. The macro I have runs both previous commands for me but it doesn’t yet put the focus back to the text editor. I will hopefully crack this soon but I just wanted to post all this first.

Here’s the macro. Remember to bind it to your shortcut of choice.

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics

Public Module ChainedCommands

    Public Sub RunSelectedUnitTestSession()
        Dim selection As TextSelection = CType(DTE.ActiveDocument.Selection(), EnvDTE.TextSelection)
        Dim textPoint As TextPoint = CType(selection.ActivePoint, TextPoint)
        Dim line As Integer = textPoint.Line
        Dim offset As Integer = textPoint.LineCharOffset
        Dim index As Integer


        selection.MoveToLineAndOffset(line, offset)
    End Sub

End Module

I’ve been using a BDD macro based on the one JP Boodhoo published for getting more descriptive test names along with a R# template so that I can type “test” + tab, type the test name (with spaces) and then hit Alt+- and voila – BDD style test names.

This worked well for a while until some point where R# started to do autocompletion on the test name as I typed (for instance, typing ‘Check that blah is not null’ often ended up as ‘Check that blah is NotImplementedException null’). That was OK though after a quick rework of the R# template and macro so R# wraps the test name in quotes which the macro then removes.

But R# doesn’t format the end result as well (the method braces don’t indent correctly) and this bugged me for a while.

So, a quick change to the macro and the R# template and now it’s much more smooth. Typing ‘tests’ + tab and R# outputs public class “FIXTURENAME” (with fixturename highlighted and ready to type over). Type out the name of the fixture, Alt+- and the macro replaces spaces, puts in the [TestFixture] attribute, drops in the opening and closed braces and puts the caret right where I want it. Sweet.

Do the same with ‘test’ + tab, type test name, Alt+- and you get the same for a test. Extra sweet.

This is the BDD macro:

   1: Imports System
   2: Imports System.Windows.Forms
   3: Imports EnvDTE
   4: Imports EnvDTE80
   5: Imports System.Diagnostics
   7: Public Module CodeEditor
   8:     Private Const INDENT_SIZE As Integer = 4
  10:     Public Sub ReplaceSpacesInTestNameWithUnderscores()
  11:         If DTE.ActiveDocument Is Nothing Then Return
  12:         Dim wrCS As Boolean = DTE.Properties("TextEditor", "CSharp").Item("WordWrap").Value
  14:         Try
  15:             DTE.Properties("TextEditor", "CSharp").Item("WordWrap").Value = False
  16:             Dim selection As TextSelection = CType(DTE.ActiveDocument.Selection(), EnvDTE.TextSelection)
  17:             Dim textPoint As TextPoint = CType(selection.ActivePoint, TextPoint)
  18:             Dim line As Integer = textPoint.Line
  19:             Dim offset As Integer = textPoint.LineCharOffset
  20:             Dim index As Integer
  22:             selection.SelectLine()
  23:             If selection.Text = "" Then Return
  25:             Dim methodIndex As Integer = selection.Text.IndexOf("public void ")
  26:             Dim classIndex As Integer = selection.Text.IndexOf("public class ")
  28:             If (methodIndex < 0 AndAlso classIndex < 0) Then
  29:                 selection.MoveToLineAndOffset(line, offset)
  30:                 Return
  31:             End If
  33:             index = CType(IIf(methodIndex >= 0, methodIndex, classIndex), Integer)
  35:             If methodIndex >= 0 Then
  36:                 HandleTestMethod(selection, index)
  37:             Else
  38:                 HandleTestClass(selection, index)
  39:             End If
  40:         Catch ex As Exception
  41:             MsgBox(ex.Message)
  42:         Finally
  43:             DTE.Properties("TextEditor", "CSharp").Item("WordWrap").Value = wrCS
  44:         End Try
  45:     End Sub
  47:     Private Sub HandleTestClass(ByVal selection As TextSelection, ByVal index As Integer)
  48:         Dim whiteSpace As String = GetWhiteSpace(selection, index)
  49:         HandleTestNameFormatting(selection, "public class ", index)
  50:         AddAttribute(selection, index, "[TestFixture]", whiteSpace)
  51:         AddBraces(selection, index, whiteSpace)
  52:     End Sub
  54:     Private Sub HandleTestMethod(ByVal selection As TextSelection, ByVal index As Integer)
  55:         Dim whiteSpace As String = GetWhiteSpace(selection, index)
  56:         HandleTestNameFormatting(selection, "public void ", index)
  57:         AddAttribute(selection, index, "[Test]", whiteSpace)
  58:         AddBraces(selection, index, whiteSpace)
  59:     End Sub
  61:     Private Sub AddAttribute(ByVal selection As TextSelection, ByVal index As Integer, ByVal attribute As String, ByVal whiteSpace As String)
  62:         selection.LineUp()
  63:         selection.SelectLine()
  64:         If selection.Text.Trim <> attribute Then
  65:             selection.Insert(selection.Text & whiteSpace & attribute & vbCrLf)
  66:         End If
  67:         selection.LineDown()
  68:     End Sub
  70:     Private Function AddIndent(ByVal spacer As Char) As String
  71:         Return String.Empty.PadRight(INDENT_SIZE, spacer)
  72:     End Function
  74:     Private Sub AddBraces(ByVal selection As TextSelection, ByVal index As Integer, ByVal whiteSpace As String)
  75:         selection.SelectLine()
  76:         If selection.Text.Trim <> "{" Then
  77:             selection.Insert(String.Format("{0}{4}{1}{0}{2}{1}{0}{5}{1}{3}", whiteSpace, vbCrLf, AddIndent(whiteSpace(0)), selection.Text, "{", "}")) ' & whiteSpace & AddIndent(whiteSpace(0)) & vbCrLf & "}" & vbCrLf)
  78:             selection.LineUp()
  79:             selection.LineUp()
  80:             selection.LineUp()
  81:             selection.EndOfLine()
  82:         Else
  83:             selection.LineUp()
  84:             selection.EndOfLine()
  85:         End If
  86:     End Sub
  88:     Private Function GetWhiteSpace(ByVal selection As TextSelection, ByVal index As Integer) As String
  89:         Return selection.Text.Substring(0, index)
  90:     End Function
  92:     Private Sub HandleTestNameFormatting(ByVal selection As TextSelection, ByVal prefix As String, ByVal index As Integer)
  93:         Dim whiteSpace As String = GetWhiteSpace(selection, index)
  94:         prefix = whiteSpace + prefix
  95:         Dim description As String = selection.Text.Replace(prefix, String.Empty).Trim
  96:         If description(0) = """" Then
  97:             Dim lastQuote As Integer = description.LastIndexOf("""")
  98:             If lastQuote = 0 Then
  99:                 description = description.Substring(1)
 100:             Else
 101:                 Dim endOfDescription As String = description.Substring(lastQuote + 1)
 102:                 description = description.Substring(1, lastQuote - 1) + endOfDescription
 103:             End If
 104:         End If
 105:         Dim text As String = prefix + description.Replace(" ", "_").Replace("'", "_") + vbCrLf
 106:         selection.Delete()
 107:         selection.Insert(text)
 108:         selection.LineUp()
 109:     End Sub
 110: End Module

For the ReSharper templates, open up the live templates and add a template called ‘tests’ with the body of template as below:

public class "$FIXTURENAME$"

And then another one called ‘test’ with the body as

public void "$TESTNAME$"()

Mark both of these as available for C# only (or if for VB.Net, adjust the template body for the VB equivalent), and for the ‘test’ one, mark this as available only where a method declaration is allowed.

That’s it. Now you can get into the flow of typing ‘test’ TAB ‘name of test’ Alt+- and then typing in the test itself.