Tuesday, February 12, 2008

How to Test a Refactoring

For this post I am going to assume that you are familiar with the basic process of unit testing using a tool such as NUnit.

I will now lead those who are interested through how I have created a simple test around Create Handler Stub.

Project Setup
The basic procedure for creating a suitable Refactor test project is:

  • Create a new Plugin Project
  • Remove the default plugin class
  • Add Reference to DevExpress.DXCore.Testing.dll
  • Add Reference to DevExpress.Refactor.Testing.dll
  • Add a class which inherits from DevExpress.Refactor.Testing.RefactoringTestFixture
  • Implement readonly string properties : 'FixtureName' , 'RefactoringName' and 'Language'

Next you will need to add suitable tests to your class.
Typically you are looking to test 2 things.

1.> The refactoring is/isnot available in a given situation
2.> The refactoring has a given effect on a given set of code.

Test by Example
DevExpress' Refactoring testing uses a very intuitive 'test by Example' style for it's testing

I call it 'test by example' because that's exactly what you do. You add example code files as resources to the project showing when you expect a refactoring to be available and what you expect to happen when it is activated.

Resource Files
The Resource files are simple code examples with small quantities of marked up used to provide the test framework with additional information such as the locate of the caret or an indication of what is selected.

[Note: These Resource files cannot be compiled in the normal fashion. The VB and C# compilers have no idea how to deal with the markup. Instead these files need have their 'Build Action' set to 'Embedded Resource'. They will be read later by the testing environment]

CheckAvailability Test (Source code)
So for our first test we are going to check availability for a given set of source. The Test Source is as follows:
Namespace Test
    Public Class TestClass
        Public Sub TestMethod()
            Dim X As New System.Windows.Forms.Button
            AddHandler X.Click, AddressOf OnClick<<:caret:>>
        End Sub
    End Class
End Namespace

The <<:caret:>> markup represents (strangely enough) the position of the caret in the test scenario. The << is not the « from CodeRush fields but instead just 2 sets of <.

So we save the above source code as 'Availability_1.vb' in a folder called  "Examples".

Next we have to create the Test itself. 
<Test("Availability for incomplete AddHandler Statement", "Refactor_CreateStubForHandler_Tests", "Availability_1.vb")> _
Public Sub TestAvailability_1(ByVal sender As Object, ByVal ea As TestEventArgs)
    Call AssertAvailable()
End Sub

.. I expect the previous code to be mangled by BlogSpot but the overall gist is simple.

In the constructor for the Test attribute, I have passed the name of the test, the path to the embedded resource needed and the name of the resource file used in this test.

[Note: When more than one test exists per refactoring, it is more than reasonable to extract the resource path out into a constant]

The call to AssertAvailable uses the Test attribute defined on the test to determine the location of the resource file which represents the scenario to test.

When it runs, it locates the resource and constructs the scenario in question, places the cursor at the given location and then tests the availability of the refactoring.

Running your test
In order to run the test you will require 'DX_TestRunner.dll' (Available by request through support@DevExpress.com according to Mark Miller). This should be placed in your plugin directory and when you run a copy of VS, you'll have an additional diagnostic ToolWindow available called 'Test Runner'

This looks like this:


You can now highlight your test or fixture and click the tick mark to check if your refactoring is available when it should be. If all goes to plan you should see something that looks like the following:


...together with a main editor window which confirms the results.


So that's how to make sure that your refactoring is available when you said it should be, without having to manually check each and every situation manually.

Next time I'll expand on how to check the your refactoring affects your code in the way that you said it should.

[Important Note: None of this information would be here if it were not for the very generous cooperation of Mr Alex Zaharov and Mr Mark Miller who lets face it, have a lot more to do than answer my questions. Thanks very much for your time guys]