Testing conventions

I already blogged about the topic of validating conventions in the past (here and here). Doing this has been a fantastic way of keeping consistency across codebases I’ve worked on, and several of my colleagues at Readify adopted this approach with great success.

Recently I found myself using this approach even more often and in scenarios I didn’t think about initially. Take this two small tests I wrote today for Windsor.

[TestFixture]
public class ConventionVerification
{
    [Test]
    public void Obsolete_members_of_kernel_are_in_sync()
    {
        var message = new StringBuilder();
        var kernelMap = typeof(DefaultKernel).GetInterfaceMap(typeof(IKernel));
        for (var i = 0; i < kernelMap.TargetMethods.Length; i++)
        {
            var interfaceMethod = kernelMap.InterfaceMethods[i];
            var classMethod = kernelMap.TargetMethods[i];
            Scan(interfaceMethod, classMethod, message);
        }
 
        Assert.IsEmpty(message.ToString(), message.ToString());
    }
 
    [Test]
    public void Obsolete_members_of_windsor_are_in_sync()
    {
        var message = new StringBuilder();
        var kernelMap = typeof(WindsorContainer).GetInterfaceMap(typeof(IWindsorContainer));
        for (var i = 0; i < kernelMap.TargetMethods.Length; i++)
        {
            var interfaceMethod = kernelMap.InterfaceMethods[i];
            var classMethod = kernelMap.TargetMethods[i];
            Scan(interfaceMethod, classMethod, message);
        }
 
        Assert.IsEmpty(message.ToString(), message.ToString());
    }
 
    private void Scan(MethodInfo interfaceMethod, MethodInfo classMethod, StringBuilder message)
    {
        var obsolete = EnsureBothHave<ObsoleteAttribute>(interfaceMethod, classMethod, message);
        if (obsolete.Item3)
        {
            if (obsolete.Item1.IsError != obsolete.Item2.IsError)
            {
                message.AppendLine(string.Format("Different error levels for {0}", interfaceMethod));
            }
            if (obsolete.Item1.Message != obsolete.Item2.Message)
            {
                message.AppendLine(string.Format("Different message for {0}", interfaceMethod));
                message.AppendLine(string.Format("\t interface: {0}", obsolete.Item1.Message));
                message.AppendLine(string.Format("\t class    : {0}", obsolete.Item2.Message));
            }
        }
        var browsable = EnsureBothHave<EditorBrowsableAttribute>(interfaceMethod, classMethod, message);
        {
            if (browsable.Item3 == false)
            {
                message.AppendLine(string.Format("EditorBrowsable not applied to {0}", interfaceMethod));
                return;
            }
            if (browsable.Item1.State != browsable.Item2.State || browsable.Item2.State != EditorBrowsableState.Never)
            {
                message.AppendLine(string.Format("Different/wrong browsable states for {0}", interfaceMethod));
            }
        }
    }
 
    private static Tuple<TAttribute, TAttribute, bool> EnsureBothHave<TAttribute>(MethodInfo interfaceMethod, MethodInfo classMethod, StringBuilder message)
        where TAttribute : Attribute
    {
        var fromInterface = interfaceMethod.GetAttributes<TAttribute>().SingleOrDefault();
        var fromClass = classMethod.GetAttributes<TAttribute>().SingleOrDefault();
        var bothHaveTheAttribute = true;
        if (fromInterface != null)
        {
            if (fromClass == null)
            {
                message.AppendLine(string.Format("Method {0} has {1} on the interface, but not on the class.", interfaceMethod, typeof(TAttribute)));
                bothHaveTheAttribute = false;
            }
        }
        else
        {
            if (fromClass != null)
            {
                message.AppendLine(string.Format("Method {0} has {1}  on the class, but not on the interface.", interfaceMethod, typeof(TAttribute)));
            }
            bothHaveTheAttribute = false;
        }
        return Tuple.Create(fromInterface, fromClass, bothHaveTheAttribute);
    }
}

 

All they do is ensure that whenever I obsolete a method on the container, I do that consistently between the interface and the class that implements it (setting the same warning message, and the same warning/error flag state). It also validates that I hide the obsolete method from Intellisense for people who have the option enabled in their Visual Studio.

 

Those are kinds of things, that are important, but they neither cause a compiler error, or compiler warning, nor do they fail at runtime. Those are kinds of things you can validate in a test. Those are small things that make a big difference, and having a comprehensive battery of tests for conventions in your application, can greatly improve confidence and morale of the team.


Posted 03-09-2011 4:52 AM by Krzysztof Koźmic
Filed under:

[Advertisement]

Comments

Chris Kemp wrote re: Testing conventions
on 03-25-2011 7:46 AM

I do the same for nHibernate, which requires virtual methods and properties.  I have a test that ensure this is the case, so that I don't have to wait until I run the program and nHibernate throws an error.

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)