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)); } } else { return; } 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.