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:

  • Documentation
  • Layout
  • Maintainability
  • Naming
  • Ordering
  • Readability
  • Spacing

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:

  • Lines 3–6: Creation of StyleCop’s types I needed to parse the code file.
  • Line 8: Due to some design fluke I needed a CodeDocument to pass to the parser. Unfortunately CodeDocument is an abstract class so I just inherited it in a dummy class I’ve created. No need to implement anything because this instance will be replaced after the new command.
  • Line 10: parse the source file – and that’s it.

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:

  • Line 2: I’ve created a new project that contains my test classes. I’ve used MSTest Deploy to make sure the file I want will be copies to the place the tests are run.
  • Lines 5–6: I don’t really need to explain that one – right?
  • Line 8: invoking the helper method
  • Line 10: Create a new instance of the class that holds the custom rules
  • Line 12: Using Isolator to make sure that AddViolation does not get called. In order to call it I would have needed to initialize a lot more of StyleCop’s environment and instead I’ve used this simple line
  • Line 14: Call my method under test
  • Lines 16-19: Using Isolator to test  that AddViolation was called and that the correct string was passes.

 

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.

5 thoughts on “Unit testing custom StyleCop rules using Typemock Isolator

  1. Can you post the code for DummyCodeDocument? I followed this and was unable to get my code to run correctly.

  2. It's been a while and I couldn't find it, the company where I wrote this code went bankrupt.
    However the DummyCodeDocument is just an empty class implementing CodeDocument:
    “CodeDocument is an abstract class so I just inherited it in a dummy class I’ve created. No need to implement anything because this instance will be replaced after the new command.”

  3. what namespace you used to access “Isolate.WhenCalled” I am getting error “The name 'Isolate' does not exist in the current context” in function “AnalyzeDocument_FileNameDoesNotMustMatchClassNameRule_AddViolationCalledWithCorrectRule”

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.