Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Dynamic Unit Testing

5.00/5 (2 votes)
1 Sep 2011CPOL3 min read 29.3K   173  
Dynamic Unit Testing

Introduction

While writing Unit Tests for an application with a complex business logic, I came across some classes that were exposing very few public methods, and had a bunch of private methods. Since the core logic was mostly embedded in those private methods, I thought I couldn't skip them in the tests. So I started to seek for a way for invoking them in my Test project. I found out that MsTest already has some shortcuts for invoking private methods (namely the PrivateObject and the PrivateType classes). Unfortunately, I was already familiar with NUnit and didn't want to switch to another testing framework in the middle of the development. I was tempted to mark the private methods as internal, since the methods with that access modifier can be invoked from outside the assembly they reside, if one marks the assembly itself with the InternalsVisibleTo attribute, in the following way (code from AssemblyInfo.cs):

C#
[assembly: InternalsVisibleTo("Business.Tests")]

Background

After having replaced some modifier from private to internal, I soon felt some kind of frustration, since I saw that I was widening the scope of those members only for my tests. So I thought that Reflection could be a better way for accomplishing that task. Here is a simple example of a private method to be tested:

C#
public class Range<T> : IRange<T> where T : IComparable<T>
{
    public T Start { get; set; }
    public T End { get; set; }

    //... other methods omitted

    private static bool Overlaps(IRange<T> first, IRange<T> second)
    {
        return first.Start.Between(second) || first.End.Between(second);
    }
}

And here's the Test method that uses Reflection for invoking it (it uses NUnit version 2.5.10):

C#
[Test,
Description("Tests two ranges that don't overlap.")]
public void OverlapTest([Random(0, 1000, 5)] int firstStart,
                        [Random(1001, 2000, 5)] int firstEnd,
                        [Random(2001, 5000, 5)] int secondStart,
                        [Random(5001, 10000, 5)] int secondEnd)
{
    var methodInfo = typeof(Range<int>).GetMethod
	("Overlaps", BindingFlags.NonPublic | BindingFlags.Static);
    var result = methodInfo.Invoke(null, new[]
                                {
                                    new Range<int>(firstStart, firstEnd),
                                    new Range<int>(secondStart, secondEnd)
                                });
    Assert.False((bool)result);
}

I knew from earlier experiences how a naive use of Reflection can dramatically slow down the procedures that rely on it. In the particular case, the use of random values as parameters results in multiple calls to the Test method, and subsequently to the Type.GetMethod method. Now, one could also ignore performance issues on Unit Tests, but here there was also a verbosity issue. Using the above syntax results in a lot of "ceremonial" code. So I began considering to use a new feature of C# 4, namely the dynamic keyword and the DynamicObject class. I had just finished the reading of the nice article Understanding the Dynamic Keyword in C# 4 that shows some examples of how the COM methods of Microsoft Excel get called more concisely using the dynamic keyword. I thought it might be useful for my unit tests too. So I wrote the DynamicObjectBase class:

C#
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;

namespace Business.Test
{
    public class DynamicObjectBase: DynamicObject
    {
        private readonly Type _type;
        private readonly Object _instance;

        private readonly Dictionary<string, MethodInfo> _methods =
                new Dictionary<string, MethodInfo>();

        public DynamicObjectBase(Object instance)
        {
            _instance = instance;
            _type = instance.GetType();
        }

        public DynamicObjectBase(Type type)
        {
            _instance = null;
            _type = type;
        }

        public override bool TryInvokeMember
        (InvokeMemberBinder binder, object[] args, out object result)
        {
            if (!_methods.ContainsKey(binder.Name))
            {
                var methodInfo = _type.GetMethod(binder.Name,
                BindingFlags.NonPublic | BindingFlags.Static |
                BindingFlags.Instance | BindingFlags.Public);
                _methods.Add(binder.Name, methodInfo);
            }

            var method = _methods[binder.Name];
            result =  method.Invoke(method.IsStatic ? null : _instance, args);

            return true;
        }
    }
}

Using the Code

The code above overrides the TryInvokeMember method, that gets called every time a method is invoked on an instance of DynamicObjectBase declared as dynamic.

The class uses a cache of MethodInfo objects, and it accepts in its constructors either the instance or the type of the class where to seek the private methods.

If a class contains only static methods, we can call the constructor that accepts a Type instance. Instead, if we are going to test instance private methods, it is mandatory to use the constructor that accepts Object as a parameter. In the Test class, we can create an instance of DynamicObjectBase and invoke the private methods on it, whether instance or static they are. So the Type.GetMethod method gets called only once, and in the next calls it uses the cached instance of MethodInfo. However, the call to MethodInfo.Invoke remains, and it's the most expensive in term of CPU load. So the performance test shows only a very little gain in the execution time. But we've stripped away the messy reflection code from the test, so look how less verbose is the calling code in the rewritten Unit Test:

C#
private readonly dynamic _range;

public RangeFixture()
{
    _range = new DynamicObjectBase(typeof(Range<int>));
}

[Test,
Description("Tests two ranges that don't overlap.")]
public void DynamicOverlapTest([Random(0, 1000, 5)] int firstStart,
                        [Random(1001, 2000, 5)] int firstEnd,
                        [Random(2001, 5000, 5)] int secondStart,
                        [Random(5001, 10000, 5)] int secondEnd)
{
    var result = _range.Overlaps(new Range<int>(firstStart, firstEnd),
                                new Range<int>(secondStart, secondEnd));
    Assert.False(result);
}

Points of Interest

The DynamicObject class fits well not only for interacting with objects that require a runtime type checking (once called "late binding"), but also for hiding some verbose Reflection code that dynamically invokes the members of a class. It is good for writing concise and readable Unit Tests, like in the scenario depicted above.

History

  • 2011 August, 30 - First release
  • 2011 August, 31 - Simplified the example code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)