14 March 2017

Using Roslyn to Automate Refactoring

Automation is a powerful tool for developers. Shell scripts, for example, can automate a system and save countless hours of time. But what if we wanted to automate changes across a codebase? IDE refactorings might not cut it, depending on the nature of the change.

For C# developers, it's no problem! Roslyn, the C# compiler, is also a library that can parse C# code and expose the syntax tree (and more!). When combined with LINQ, this makes it easy to automate changes across a codebase.

Here at Jetabroad we've used Roslyn several times to make large codebase refactors. In this post, we'll use Roslyn to make a basic NUnit test to xUnit test converter. In the interest of keeping this post short, we won't get into NUnit vs xUnit, but there are plenty of comparisons on the web. We'll also be assuming some basic knowledge of Roslyn.

Building our NUnit to xUnit Converter

There are many differences between NUnit and xUnit, and not all of them are easily automatable. However, we can save a lot of time by automating the following changes:
  • NUnit test methods have a [Test] attribute. xUnit test methods use a [Fact] attribute.
  • NUnit test classes have a [TestFixture] attribute. xUnit test classes don't need an attribute.
  • NUnit has [SetUp] for test initialization and [TearDown] for cleanup. xUnit uses the the constructor for initialization and IDisposable for cleanup.
It's completely fine to start slinging some LINQ to make the above changes. However, there's a more structured way, that could lead to more maintainable code: the CSharpSyntaxRewriter.

The CSharpSyntaxRewriter is built into Roslyn. It's an implementation of the Visitor pattern, and it provides an elegant way to traverse and "update" our immutable syntax tree. Here's what a sample syntax tree looks like for an NUnit test class:

NUnit syntax tree

Let's take the easy case first, and replace [Test] with [Fact]:

We subclass the CSharpSyntaxRewriter, and override one of the many methods of the base class. In this case, we want to make a change to an attribute so we override VisitAttribute, and return an xUnit Fact attribute to replace the NUnit Test attribute. If it's not an attribute we're interested in, we call the base method to continue processing other nodes of the tree.

Pretty straight-forward, right? But so far, a Find-Replace would do the same thing with less effort. Let's move on to something more interesting: deleting [TestFixture]. In this case, we need to override VisitAttributeList, because if we remove TestFixture from the attribute list [TestFixture] we'd end up with the empty attribute list []. So we need to remove the entire attribute list in that case:

Finally, let's do something a bit more difficult, replacing the method with the SetUp attribute with a constructor:

Easy! We now have a basic CSharpSyntaxRewriter implementation. Here's how we might call it on a project:

Future Work

For the next steps in our xUnit converter, we need to do the following:
  • Converting TearDown to IDisposable
  • Adding using Xunit; to the top of each file.
  • Converting TestCaseSource to xUnit's MemberData
However, these changes follow the same conceptual line as the covered changes, so they're left as an exercise for the reader. Happy hacking!