Posts Tagged ‘testing’

Testing framework is not just for writing… tests

Wednesday, January 18th, 2012

Quick ques­tion – from the top of your head, with­out run­ning the code, what is the result of:

var foo = -00.053200000m;
var result = foo.ToString("##.##");

Or a dif­fer­ent one:

var foo = "foo";
var bar = "bar";
var foobar = "foo" + "bar";
var concaternated = new StringBuilder(foo).Append(bar).ToString(); 

var result1 = AreEqual(foobar, concaternated);
var result2 = Equals(foobar, concaternated);

public static bool AreEqual(object one, object two)
{
    return one == two;
}

How about this one from NHibernate?

var parent = session.Get<Parent>(1); 

DoSomething(parent.Child.Id); 

var result = NHibernateUtil.IsInitialized(parent.Child);

The point being?

Well, if you can answer all of the above with­out run­ning the code, we’re hir­ing. I don’t, and I sus­pect most peo­ple don’t either. That’s fine. Ques­tion is – what are you going to do about it? What do you do when some 3rd party library, or part of stan­dard library exhibits unex­pected behav­iour? How do you go about learn­ing if what you think should hap­pen, is really what does happen?

Scratch­pad

I’ve seen peo­ple open up Visual Stu­dio, cre­ate ConsoleApplication38, write some code using the API in ques­tion includ­ing plenty of Console.WriteLine along the way (curse who­ever decided Client Pro­file should be the default for Con­sole appli­ca­tions, switch to full .NET pro­file) com­pile, run and dis­card the code. And then repeat the process with ConsoleApplication39 next time.

 

The solu­tion I’m using feels a bit more light­weight, and has worked for me well over the years. It is very sim­ple – I lever­age my exist­ing test frame­work and test run­ner. I cre­ate an empty test fix­ture called Scratchpad.

scratchpad

scratchpad_fixture

This class gets com­mit­ted to the VCS repos­i­tory. That way every mem­ber of the team gets their own scratch­pad to play with and val­i­date their the­o­ries, ideas and assump­tions. How­ever, as the name implies, this all is a one-off throw­away code. After all, you don’t really need to test the BCL. One would hope Microsoft already did a good job at that.

If you’re using git, you can eas­ily tell it not to track changes to the file, by run­ning the fol­low­ing com­mand (after you com­mit the file):

git update-index –assume-unchanged Scratchpad.cs

scratchpad_git

With this sim­ple set up you will have quick place to val­i­date your assump­tions (and answer ques­tions about API behav­iour) with lit­tle friction.

scratchpad_test

So there you have it, a new, use­ful tech­nique in your toolbelt.

Approval testing – value for the money

Monday, January 16th, 2012

I am a believer in the value of test­ing. How­ever not all tests are equal, and actu­ally not all tests pro­vide value at all. Raise your hand if you’ve ever seen (unit) tests that tested every cor­ner case of triv­ial piece of code that’s used once in a blue moon in an obscure part of the sys­tem. Raise your other hand if that test code was not writ­ten by human but generated.

 

As with any type of code, test code is a lia­bil­ity. It takes time to write it, and then it takes even more time to read it and main­tain it. Con­sid­er­ing time is money, rather then blindly unit test­ing every­thing we need to con­stantly ask our­selves how do we get the best value for the money – what’s the best way to spend time writ­ing code, to write the least amount of it, to best cover the widest range of pos­si­ble fail­ures in the most main­tain­able fashion.

Notice we’re opti­mis­ing quite a few vari­ables here. We don’t want to blindly write plenty of code, we don’t want to write sloppy code, and we want the test code to prop­erly ful­fil its role as our safety net, alarm­ing us early when things are about to go belly up.

Test­ing conventions

What many peo­ple seem to find chal­leng­ing to test is con­ven­tions in their code. When all you have is a ham­mer (unit test­ing) it’s hard to hit a nail, that not only isn’t really a nail, but isn’t really explic­itly there to being with. To make mat­ters worse the com­piler is not going to help you really either. How would it know that Login­Con­troller not imple­ment­ing ICon­troller is a prob­lem? How would it know that the new depen­dency you intro­duced onto the con­troller is not reg­is­tered in your IoC con­tainer? How would it know that the pub­lic method on your NHiber­nate entity needs to be virtual?

 

In some cases the tool you’re using will pro­vide some level of val­i­da­tion itself. NHiber­nate knows the meth­ods ought to be vir­tual and will give you quite good excep­tion mes­sage when you set it up. You can ver­ify that quite eas­ily in a sim­ple test. Not every­thing is so black and white how­ever. One of diag­nos­tics pro­vided by Cas­tle Wind­sor is called “Poten­tially mis­con­fig­ured com­po­nents”. Notice the vague­ness of the first word. They might be mis­con­fig­ured, but not nec­es­sar­ily are – it all depends on how you’re using them and the tool itself can­not know that. How do you test that efficiently?

Enter approval testing

One pos­si­ble solu­tion to that, which we’ve been quite suc­cess­fully using on my cur­rent project is approval test­ing. The con­cept is very sim­ple. You write a test that runs pro­duc­ing an out­put. Then the out­put is reviewed by some­one, and assum­ing it’s cor­rect, it’s marked as approved and com­mit­ted to the VCS repos­i­tory. On sub­se­quent runs the out­put is gen­er­ated again, and com­pared against approved ver­sion. If they are dif­fer­ent the test fails, at which point some­one needs to review the change and either mark the new ver­sion as approved (when the change is legit­i­mate) or fix the code, if the change is a bug.

 

If the expla­na­tion above seems dry and abstract let’s go through an exam­ple. Wind­sor 3 intro­duced way to pro­gram­mat­i­cally access its diag­nos­tics. We can there­fore write a test look­ing through the poten­tially mis­con­fig­ured com­po­nents, so that we get noti­fied if some­thing on the list changes. I’ll be using Approval­Tests library for that.

[Test]
pub­lic void Approved_potentially_misconfigured_components()
{
    var con­tainer = new Wind­sor­Con­tainer();
    container.Install(FromAssembly.Containing<HomeController>());

    var han­dlers = GetPotentiallyMisconfiguredComponents(container);
    var mes­sage = new String­Builder();
    var inspec­tor = new DependencyInspector(message);
    fore­ach (IEx­poseDe­pen­den­cy­Info han­dler in han­dlers)
    {
        handler.ObtainDependencyDetails(inspector);
    }
    Approvals.Approve(message.ToString());
}

pri­vate sta­tic IHan­dler[] GetPotentiallyMisconfiguredComponents(WindsorContainer con­tainer)
{
    var host = container.Kernel.GetSubSystem(SubSystemConstants.DiagnosticsKey) as IDi­ag­nos­tic­sHost;
    var diag­nos­tic = host.GetDiagnostic<IPotentiallyMisconfiguredComponentsDiagnostic>();
    var han­dlers = diagnostic.Inspect();
    return han­dlers;
}

What’s impor­tant here is we’re set­ting up the con­tainer, get­ting the mis­con­fig­ured com­po­nents out of it, pro­duce read­able out­put from the list and pass­ing it down to the approval frame­work to do the rest of the job.

Now if you’ve set up the frame­work to pup-up a diff tool when the approval fails you will be greeted with some­thing like this:

approval_diff

You have all the power of your diff tool to inspect the change. In this case we have one new mis­con­fig­ured com­po­nent (Home­Con­troller) which has a new para­me­ter, appro­pri­ately named miss­ing­Pa­ra­me­ter that the con­tainer doesn’t know how to pro­vide to it. Now you either slap your­self in the fore­head and fix the issue, if that really is an issue, or approve that depen­dency, by copy­ing the diff chunk from the left pane to the right, approved pane. By doing the lat­ter you’re noti­fy­ing the test­ing frame­work and your team­mates that you do know what’s going on and you know it’s not an issue the way things are going to work. Cou­pled with a sen­si­ble com­mit mes­sage explain­ing why you chose to approve this dif­fer­ence you get a pretty good trail of excep­tion to the rule and rea­sons behind them.

 

That’s quite an ele­gant approach to a quite hard prob­lem. We’re using it for quite a few things, and it’s been giv­ing us really good value for lit­tle effort it took to write those tests, and main­tain them as we keep devel­op­ing the app, and the approved files change.

 

So there you have it, a new, use­ful tool in your toolbelt.

Testing conventions

Wednesday, March 9th, 2011

I already blogged about the topic of val­i­dat­ing con­ven­tions in the past (here and here). Doing this has been a fan­tas­tic way of keep­ing con­sis­tency across code­bases I’ve worked on, and sev­eral of my col­leagues at Read­ify adopted this approach with great success.

Recently I found myself using this approach even more often and in sce­nar­ios I didn’t think about ini­tially. 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 when­ever I obso­lete a method on the con­tainer, I do that con­sis­tently between the inter­face and the class that imple­ments it (set­ting the same warn­ing mes­sage, and the same warning/error flag state). It also val­i­dates that I hide the obso­lete method from Intel­lisense for peo­ple who have the option enabled in their Visual Studio.

Those are kinds of things, that are impor­tant, but they nei­ther cause a com­piler error, or com­piler warn­ing, nor do they fail at run­time. Those are kinds of things you can val­i­date in a test. Those are small things that make a big dif­fer­ence, and hav­ing a com­pre­hen­sive bat­tery of tests for con­ven­tions in your appli­ca­tion, can greatly improve con­fi­dence and morale of the team.