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!