Test Rename Macro for Visual Studio

.NET Musings

Wandering thoughts of a developer, architect, speaker, and trainer

NAVIGATION - SEARCH

Test Rename Macro for Visual Studio

NOTE: This post has been updated on 10/23/2008 to include parsing Pascal Cased names. Again on 03/30/2009 for a good catch - test methods are public. Also updated to handle all types of methods, not just public void. On 9/23/2009, the macro was rewritten to also include handling of the MSpec testing syntax.  The update is located at http://www.skimedic.com/blog/post/2009/09/23/Updated-Visual-Studio-Macro-for-MSpec-Naming.aspx.

Last year I attended Jean-Paul Boodhoo's class in New York, and one of the many cool things out of that class was a macro JP had been carrying around to rename the methods for tests.

The concept is simple. The test name should document what the test is validating, and the test name should be readable. If you are testing the Add function from my previous posts, then you would want a test named:

public void Should Add Two Positive Integers()
{
	//test stuff here 
}

 

We all know that spaces are not allowed in method names, so to keep readability high, the spaces are replaced with underscores to read:

public void Should_Add_Two_Positive_Integers()
{
     //test stuff here
}

 

Instead of the keyboard nightmare of typing underscores intermingled with Pascal casing, JP presented a macro that would take the line containing the cursor and replace the space with underscores, factoring out the "public void". While in class, I tweaked the macro to handle things like word wrapping and different languages.
I recently stumbled on an issue with parameters (as in creating a RowTest in MbUnit). It took me a while to hit it, since in true TDD fashion, I never start with a row test, but will end up there when I have some duplicate code to refactor. However, while writing a test case using the Obvious Implementation, it hit me.

public void Should Add Two Positive Integers(int first, int second, int result)
{
	//test stuff here
}

 

became:

private void Should_Add_Two_Positive_Integers(int_first,_int_second,_int_result)
{
	//test stuff here
}

 

So, back to the tweaking. After fixing that, I recently encountered a situation doing brown field enhancements where the project was significantly lacking test coverage. The method names were good (such as GetDataForBlahBlah), and easily translated to "Should_Get_Data_For_Blah_Blah". I added another method for the macro to automagically add underscores between capital letters.

Here is the new macro in it's entirety:

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Public Module TDD
    Public Sub ReplaceSpacesInTestNameWithUnderscores()
        If DTE.ActiveDocument Is Nothing Then Return
        'Use "Basic" for VB.Net    
        Dim wrCS As Boolean = DTE.Properties("TextEditor", "CSharp").Item("WordWrap").Value
        Try
            If wrCS = True Then
                DTE.Properties("TextEditor", "CSharp").Item("WordWrap").Value = False
            End If
            Dim selection As TextSelection = CType(DTE.ActiveDocument.Selection(), EnvDTE.TextSelection)
            selection.SelectLine()
            If selection.Text = "" Then Return
            Dim prefix As String = GetPrefix(selection.Text)
            If prefix = "" Then Return
            Dim suffix As String = GetSuffix(selection.Text)
            If suffix = "" Then Return
            Dim description As String = GetBody(prefix.Length, selection.Text, suffix.Length)
            If description = "" Then Return
            description = SplitOnCamelCasing(description)
            selection.Text = prefix + description.Replace(" ", "_").Replace("'", "") + suffix
            selection.LineDown()
            selection.EndOfLine()
        Catch ex As Exception
            MsgBox(ex.Message)
        Finally
            If wrCS = True Then
                DTE.Properties("TextEditor", "CSharp").Item("WordWrap").Value = wrCS
            End If
        End Try
    End Sub
    Private Function GetBody(ByVal prefixLength As Integer, ByVal selectedText As String, ByVal suffixLength As Integer) As String
        If prefixLength = 0 Or suffixLength = 0 Then
            Return String.Empty
        End If
        Return selectedText.Substring(prefixLength, selectedText.Length - prefixLength - suffixLength)
    End Function
    Private Function GetSuffix(ByVal selectedText As String) As String
        Dim indexOf As Integer = selectedText.IndexOf("(")
        If indexOf < 0 Then
            Return String.Empty
        End If
        Return selectedText.Substring(indexOf)
    End Function
    Private Function GetPrefix(ByVal selectedText As String) As String
        Dim words As String() = selectedText.Split(New String() {" "}, StringSplitOptions.RemoveEmptyEntries)
        For x As Integer = 0 To words.Length
            Select Case words(x)
                Case "public", "private", "internal"
                    If x = words.Length - 1 Then Return String.Empty
                    If words(x + 1) <> "static" Then
                        Return selectedText.Substring(0, selectedText.IndexOf(words(x + 1))) & words(x + 1) & " "
                    Else
                        If x + 2 = words.Length - 1 Then Return String.Empty
                        Return selectedText.Substring(0, selectedText.IndexOf(words(x + 2))) & words(x + 2) & " "
                    End If
                Case Else
                    Return String.Empty
            End Select
        Next
        Return String.Empty
    End Function
    Private Function SplitOnCamelCasing(ByVal description As String)
        For x As Integer = description.Length - 1 To 0 Step -1
            If x <> 0 And x <> description.Length Then
                Dim c As String = description.Substring(x, 1)
                Dim pc As String = description.Substring(x - 1, 1)
                If c <> "_" And c <> " " And c.ToUpper() = c And pc <> "_" And pc <> " " Then
                    description = description.Insert(x, " ")
                End If
            End If
        Next
        Return description
    End Function
End Module

 

To wire it up, add this macro through Macro Explorer, then in VS, select Tools,Options, Environment,Keyboard, select your macro in the "Show commands containing:" drop down, and assign a key combination (I like "Alt+=" - it doesn't interfere with ReSharper or VS).

Then, write your test, place the cursor on the method declaration line, and hit you key-combo. It's that simple!


Happy Coding!

Comments (3) -

Scott White

Nice, yea I think it was after a good session TechEd last year that I started naming my Unit Tests with underscores like this.  It definitely makes sense.

Philip Japikse, MCSD, MCDBA, CSM

Good catch.  I completely refactored it so it will handle any c# method in the format of(public|private|internal) [static] (return type)

Very useful - thanks. It's a trivial suggestion but it might be worth changing the visibilty of your code examples to "public" since the macro as stands does nothing for private methods. Being new to Visual Studio and VS macros, I tested it out on the examples above the code snippet to see if I'd wired it up correctly and it took me a moment to realise why it wasn't working. Then again, maybe it's good to make your readers work a little Smile

Comments are closed
Managed Windows Shared Hosting by OrcsWeb