Unit testing custom StyleCop rules using Typemock Isolator

I’ve never was a big fan of “coding standard” – Although I always thought that the same style should be kept throughout a project or even the entire company’s code base - the idea of forcing developers to write the same code based on a document nobody ever read seemed just wrong.

Fast forward a few years and suddenly I’m responsible that my team’s code will be written according to the coding standard of the company.

At first I thought it shouldn’t be too hard – everybody knows the coding standard – and boy was I wrong. The coding standard document was copied from a previous document and even the developers that did read it couldn’t remember all of it’s 20+ pages of rules and ideas.

It was clear that I needed help – preferably in a form of some tool that would process my team’s code. After looking a bit I’ve found that tool – named StyleCop.

What is StyleCop

StyleCop is a free static code analysis tool from Microsoft that checks C# code for conformance to StyleCop's recommended coding styles and a subset of Microsoft's .NET Framework Design Guidelines. StyleCop analyzes the source code, allowing it to enforce a different set of rules from FxCop. The rules are classified into the following categories:

StyleCop includes both GUI and command line versions of the tool. It is possible to create new rules to be used.

StyleCop was re-released as an open source project in April of 2010, at http://stylecop.codeplex.com.

[From Wikipedia]

On the process of implementing custom StyleCop rules

Back to the problem at hand – although my company has a coding standard it’s not exactly similar to Microsoft’s so I needed to develop some custom rules luckily this topic was already covered by my fellow bloggers:

A custom Rule would look something like:

[SourceAnalyzer(typeof(CsParser))]
public class NamingRules : SourceAnalyzer
{
public override void AnalyzeDocument(CodeDocument document)
{
var csdocument = (CsDocument)document;

if (csdocument.RootElement != null && !csdocument.RootElement.Generated)
{
csdocument.WalkDocument(VisitElement, null, null);
}
}

private bool VisitElement(CsElement element, CsElement parentElement, object context)
{
if (element.Generated)
{
return true;
}

if(element.ElementType == ElementType.Class && !(element.Parent is Class))
{
var csClass = (Class)element;

var fileName = csClass.Document.SourceCode.Name;
var fileNameWithoutExtension = string.Format("class {0}", Path.GetFileNameWithoutExtension(fileName));
var className = csClass.GetShortName();

if(fileNameWithoutExtension.Equals(className) == false)
{
AddViolation(element, element.LineNumber, "FileNameMustMatchClassName");
}
}

return true;
}
}


In case you were wondering the code above checks that a class resides in a file with the same name.



 



But there is a problem with writing style rules – they look trivial at first but tend to accumulate corer cases as you progress. My solution was to find a way to test the custom rules I’ve written so that I won’t accidently break during my work.



The added value of using unit tests is that I didn’t need to manually test my new rules – a process that consists from the following steps:




  1. Implement a new style rule


  2. Compile the custom rule assembly


  3. Copy the assembly to StyleCop’s folder


  4. Open a new instance of Visual Studio


  5. Write code to test the new rule


  6. Run the new rule


  7. More often than not – find a bug. close visual studio and go to step #1



Instead I got the following:




  1. Write failing test


  2. Run test


  3. Implement a new style rule


  4. Run the test again


  5. If test still fail go to step #1



Better, Simpler, Faster



Writing unit tests for my custom rules



I’ve wanted to be able to parse an actual file and analyze it using StyleCop and my new rules – using some Reflector magic I was able to discover how StyleCop worked and I was able to write the following “helper” method:





   1:  public static CodeDocument ParseDocument(string codeFileName, string projectFileName) 


   2:  {


   3:     var parser = new CsParser();


   4:     var configuration = new Configuration(null);


   5:     var project = new CodeProject(projectFileName.GetHashCode(), projectFileName, configuration);


   6:     var sourceCode = new CodeFile(codeFileName, project, parser);


   7:   


   8:     CodeDocument document = new DummyCodeDocument(sourceCode);


   9:     


  10:     parser.ParseFile(sourceCode, 0, ref document);


  11:     


  12:     return document;


  13:  }



The method receives a file name and a project name and creates StyleCop’s representation of that file.



Details:





Armed with a method that enable me to parse code files I was now able to test my new rule – almost, I’ve needed to fake a call to AddViolation and verify it got called and for that I’ve used Typemock Isolator:





   1:  [TestMethod]


   2:  [DeploymentItem(@"..\..\..\.StyleCop.Rules.Tests.TestClasses\FileNameMustMatchClassNameRule.cs")]


   3:  public void AnalyzeDocument_FileNameDoesNotMustMatchClassNameRule_AddViolationCalledWithCorrectRule() 


   4:  {


   5:     const string projectFileName = @"StyleCop.Rules.Tests.TestClasses.csproj";


   6:     const string codeFileName = @"FileNameMustMatchClassNameRule.cs";


   7:   


   8:     var document = TestHelpers.ParseDocument(codeFileName, projectFileName);


   9:   


  10:     var namingRules = new NamingRules();


  11:   


  12:     Isolate.WhenCalled(() => namingRules.AddViolation(null, 0, string.Empty)).IgnoreCall();


  13:   


  14:     namingRules.AnalyzeDocument(document);


  15:   


  16:     Isolate.Verify.WasCalledWithArguments(() => namingRules.AddViolation(null, 0, string.Empty))


  17:        .Matching(objects => {


  18:           var ruleName = objects[2].ToString();


  19:           return ruleName.Equals("FileNameMustMatchClassNameRule");


  20:        });


  21:  }



Details:





 



That’s enough code for now. Using this method I was able to write tests to all of the custom rules I’ve implemented.



As for my opinion about the need for an official coding style - it changed, after fixing a few (thousands) lines – the code actually look better and more importantly it’s more readable.



Labels: , , , , ,