NSort is a very nice and flexible package of sorting (15) algorithms from which the developer can choose. This library was a jointly work from me, Marc Clifton and Robert Rohde. In this blog, I'll show how NSort can be quickly "upgraded" to Generic and how you can use the extensibility of MbUnit to test it efficiently.
Generic
Converting the algorithms to Generic was really straightforward. For almost all cases, it was just a matter of adding <T> here and there, and replacing temporary object instance by T. Let's see this with the two interfaces of the project: IStorter and ISwap:
IStorter
ISwap
using System.Collections; ... public interface ISorter { IComparer Comparer {get;} void Sort(IList list); } public interface ISwap { void Swap(IList array, int left, int right); void Set(IList array, int left, int right); void Set(IList array, int left, object obj); }
Now, their generic brothers:
using System.Collections.Generic; ... public interface ISorter<T> { IComparer<T> Comparer {get;} void Sort(IList<T> list); } public interface ISwap<T> { void Swap(IList<T> array, int left, int right); void Set(IList<T> array, int left, int right); void Set(IList<T> array, int left, object obj); }
That was pretty quick. The conversion of the algorithms followed the same ideas and a dozens of cut/paste/replace later the NSort.Generic namespace was born.
Testing
There is a well-know proverb that says "the one who live by the cut-and-paste, die by the cut-and-paste" and that's exactly how I ported NSort to generics... so to ensure the quality of it really needs proper testing.
Testing NSort
Let's start with the testing of the non-generic classes. The main purpose of the NSort is to provide implementation of the ISorter interface that effectively sort list of elements and that's what we are willing to test. A quick (internal) brain strom for test cases of ISorter yields:
Since we are testing an interface, this is a good candidate to use CompositeUnitTesting the TypeFixture of MbUnit:
[TypeFixture(typeof(ISorter))] public class SorterTest { private int[] list; private void CreateSortAndCheckSorted(ISorter sorter, int length) { Random rnd = new Random(); list = new int[length]; // create data for (int i = 0; i < list.Length; ++i) list[i] = rnd.Next(); // sort table sorter.Sort(list); // verify for (int i = 0; i < list.Length - 1; ++i) { Assert.IsTrue(sorter.Comparer.Compare(list[i], list[i + 1]) <= 0, "Element {0} ({1}) is strictly greather that {1} ({2})", i, list[i], i + 1, list[i + 1] ); } } [Test] [ExpectedArgumentNullException] public void SortNulList(ISorter sorter) { sorter.Sort(null); } [Test] public void SortEmptyList(ISorter sorter) { CreateSortAndCheckSorted(sorter, 0); } [Test] public void SortListWithOneElement(ISorter sorter) { CreateSortAndCheckSorted(sorter, 1); } [Test] public void SortListWithTwoElements(ISorter sorter) { CreateSortAndCheckSorted(sorter, 2); } [Test] public void SortListOfSize100(ISorter sorter) { CreateSortAndCheckSorted(sorter, 100); } }
Now that we have defined the fixture, we need to feed it with ISorter instances. Usually, you would need to write a factory class that would create the instance, but this task was too boring not to be automated. What I want is a way to tell MbUnit to look for all the classes in NProf that implemented ISorter that apply the fixture to it...
ISorter
Implementing a custom factory attribute:
The TypeFixture fixture looks for attributes that derive from ProviderFixtureDecoratorPatternAttribute and invoke their GetRun method to gather the IRun instance. Therefore, we "just" need to inherit from this attribute and implement the behavior we want:
[AttributeUsage(AttributeTargets.Class,AllowMultiple =true,Inherited =true)] public class AssemblyProviderFactoryAttribute : ProviderFixtureDecoratorPatternAttribute { private Type assemblyType; // type contained in the assembly to test private Type targetType; // the tested type public AssemblyProviderFactoryAttribute(Type assemblyType, Type targetType) { ... this.assemblyType = assemblyType; this.targetType = targetType; } public Assembly Assembly { get { return this.assemblyType.Assembly;} } public Type TargetType { get { return this.targetType;} } public override IRun GetRun(Type decoratedType) { throw new NotImplementedException(); } }
The GetRun still needs to be implemented. The task for the returned Run object is to explore the tested Assembly (assemblyType.Assembly) for types compatible with targetType. MbUnit provides an abstract base class, Run, for implementing IRun:
GetRun
public class AssemblyProviderRun : Run { private AssemblyProviderFactoryAttribute attribute; public AssemblyProviderRun(AssemblyProviderFactoryAttribute attribute) { this.attribute = attribute; } public override void Reflect(RunInvokerTree tree, RunInvokerVertex parent, Type t) { foreach (Type type in this.attribute.Assembly.GetExportedTypes()) { if (!type.IsClass) // interrested in class only, continue; if (type.IsAbstract) // abstract class cannot be created continue; if (!this.attribute.TargetType.IsAssignableFrom(type)) // must be assignable to TargetType continue; // trying to get the default constructor ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes); if (ci == null)// no default constructor continue; ActivatorRunInvoker invoker = new ActivatorRunInvoker(this, type); tree.AddChild(parent, invoker); } } }
ActivatorRunInvoker is a IRunInvoker implementation that takes a type, creates an new instance of it and feeds it to the arguments of the next invoker. We implement this invoker by inheriting from the abstract base class RunInvoker:
public class ActivatorRunInvoker : RunInvoker { private Type targetType; public ActivatorRunInvoker(IRun run, Type targetType) :base(run) { this.targetType = targetType; } public override string Name { get {return this.targetType.Name;} } public override object Execute(object o, System.Collections.IList args) { Object target = Activator.CreateInstance(targetType); args.Add(target); return null; } }
That's the end of the journey. The attribute is ready to be used to tag the SorterTest fixture:
[TypeFixture(typeof(ISorter))] [AssemblyProviderFactory(typeof(ISorter),typeof(ISorter))] public class SorterTest { ... }
Let's launch those test and see what happens. I like having my Test assemblies "self-executable" so I make it a Console application and the AutorRunner in the Main method. The Text report spits out:
Tests run: 75, Failures: 0, Not run: 0
This makes sense because there are 15 ISorter implementation times 5 tests applied to them. Of course, I would like to have more information, so I generate the Html report:
Here we see clearly which class is being tested and so, our new attribute is working perfectly without recompiling MbUnit!
Testing NSort.Generics
In order to test the generic sort algorithm, we need to take the following steps:
The task 1 is trivial and is similar to the ISorter to ISorter<T> transformation. The second task is more technical because we need to check that types are generic and handle them differently. To cut the story short, here is the method that iterates over generic types:
private void ReflectGenericTypes(RunInvokerTree tree, RunInvokerVertex parent) { Type[] args = this.attribute.TargetType.GetGenericArguments(); foreach (Type type in this.attribute.Assembly.GetExportedTypes()) { if (!type.IsClass) continue; if (type.IsAbstract) continue; if (!type.HasGenericArguments) // true if type is a generic continue; // get the generic type definition Type genericType = type.GetGenericTypeDefinition(); // bind types Type gtype = genericType.BindGenericParameters(args); // check if assignable if (!this.attribute.TargetType.IsAssignableFrom(gtype)) continue; // create new invoker AddInvoker(tree, parent, gtype); } }
It took me a while to fix that up but I finally got though. Now, the GenericSorterTest looked as follows and I was ready to hit the "Run" button:
[TypeFixture(typeof(ISorter<int>))] [AssemblyProviderFactory(typeof(ISorter), typeof(ISorter<int>))] public class GenericSorterTest {...
The text report of the tests returned 150 tests, which was logical and they all passed (after some bug fixing). And I'm happy.
The method of testing I showed here worked well because of the nature of the problem to test: the ISorter class are all well separated and do not need mocking and so on. One of the nicest thing about this is that if you write new ISorter implementations, you do not need to modify your fixutre, they will be automatically tested by MbUnit.
Page rendered at Sunday, September 07, 2008 10:10:31 PM UTC
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.