Thursday, December 20, 2007

Creating Custom Code Analysis Rule

In VS2005, code analysis can be run very easily selecting the rules individually. There might be occasions that these rules are not enough. The rule libraries in VS2005 are created using Fxcop and you can easily create your own coding rules. I will give an example on creating a rule that will force the application to use "Test" as the prefix in unit test methods.

Fxcop dlls are located under C:\Program Files\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop. While writing the rules FxCopSdk and Microsoft.Cci dlls need to be references. Introspection library has been introduced in Fxcop but I could not see documentation enough. With the reflection and file assembler add-on, it is very easy to open the dlls and see the rule project as a whole in c# code. You will have lots of examples to look at.

I will summarize the necessary steps

1) Create a class library, UnitTestsRules class library

2) Add a base class for your unit test rules, UnitTestIntrospectionRule.cs

using Microsoft.FxCop.Sdk;
using Microsoft.FxCop.Sdk.Introspection;
using System;
namespace Microsoft.FxCop.Rules.Naming
{
internal abstract class UnitTestIntrospectionRule : BaseIntrospectionRule
{
protected UnitTestIntrospectionRule(string name)
: base(name, "UnitTestRules.UnitTestRules", typeof(UnitTestIntrospectionRule).Assembly)
{
}
internal Resolution GetNamedResolutionInternal(string id, params string[] args)
{
return this.GetNamedResolution(id, args);
}
public override TargetVisibilities TargetVisibility
{
get
{
return TargetVisibilities.All;
}
}
}
}
As seen in the example the constructor refers to a resource. This resource is an xml file with the name UnitTestRules.xml. It should be selected as an embedded resource in the project. Since the default namespace is UnitTestsRules, it is possible to access to the resource as UnitTestRules.UnitTestRules. If you have any directories in place for the resource you need to add them to the path as well.

3) Add the rule class, MethodsMustStartWithTest.cs

In this class we will write the rule to make sure all the test methods start with "Test" To do that we will overriding the method checking for members. If you are doing any code analysis for types etc you need to override the appropriate one. To test if a test method starts with Test, we first get the method info and check the method's attributes to see whether it contains TestMethodAttribute attribute or not. If there is a problem we add a resolution to the problems collection. Since it is a problem in a member, name the resolution as Member. It will match to an entry in the xml file that we created earlier. We will pass the name of the method that does not conform to the standard in the resolution.
using Microsoft.Cci;
using Microsoft.FxCop.Sdk;
using Microsoft.FxCop.Sdk.Introspection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
namespace Microsoft.FxCop.Rules.Naming
{
internal sealed class MethodsMustStartWithTest : UnitTestIntrospectionRule
{
public MethodsMustStartWithTest()
: base("MethodsMustStartWithTest")
{
}
public override ProblemCollection Check(Member member)
{
//Check if the member being processed is a method
Method method = member as Method;
if (member.NodeType != Microsoft.Cci.NodeType.Method)
{
return base.Problems;
}
//Chekc if the method is a test method
object[] info = method.GetMethodInfo().GetCustomAttributes(typeof(TestMethodAttribute), true);
if (info == null info.Length == 0)
{
return base.Problems;
}
//It is a test method, so check the name of the method to see if it starts with 'Test'
string name = member.Name.Name;
if (!name.StartsWith("Test"))
{
//Create a problem using 'Member' resolution
Problem problem = new Problem(this.GetNamedResolution("Member", new string[] { name }), name);
base.Problems.Add(problem);
}
return base.Problems;
}
}
}

4) Create the test rule content in UnitTestRules.xml

We will give a friendly name to the rule collection. It will be displayed in VS2005 using this name. We will also map the rule's type name to the name of the class that we just, MethodsMustStartWithTest.
For each rule give a different id. In this case it is UNITTEST0001. The other attributes are already self explanotory. We have a resolution named as Member and expects one paramaterwhich is the method name as explained in the previous step.

<Rules FriendlyName="Custom UnitTest Rules">
<Rule TypeName="MethodsMustStartWithTest" Category="UnitTests.Naming" CheckId="UNITTEST0001">
<Name>Methods Must Start With Test</Name>
<Description>Methods Must Start With Test</Description>
<Url>/UnitTesting/MethodsMustStartWithTest.html</Url>
<Resolution Name="Member">Method {0} does not start with Test</Resolution>
<Email>
</Email>
<MessageLevel Certainty="95">Error</MessageLevel>
<FixCategories>Breaking</FixCategories>
<Owner />
</Rule>
</Rules>

5) Deploy the rule dll

After building the dll, drop it under C:\Program Files\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\Rules. It ius ready to use from VS2005!

No comments: