Sunday, April 27, 2008

Unit-testing private methods

Suppose you have a class that has some private methods and you want to make sure these methods function correctly, so you want to write unit-tests for them. The problem is that you can't call these methods, because they are private.

The class we want to test:

public class ClassWithPrivateMethods
{
     public int Add(int firstNumber, int secondNumber)
     {
         return firstNumber + secondNumber;
     }

     private int Substract(int initialNumber, int numberToSubstract)
     {
         return initialNumber - numberToSubstract;
     }
}

Testing the "Add" function is simple. It's public, so we can just call it from a unit test:

[TestMethod]
public void TestAddition()
{
     ClassWithPrivateMethods classToTest = new ClassWithPrivateMethods();
     int result = classToTest.Add(5, 5);
     Assert.AreEqual(10, result);
}

Testing the "Substract" function is a problem. You can't call the method from the unit-test. One way around this is creating a new class, specific for the unit-test, that inherits from the class you want to test and use the "new" keyword to make the function available as a public function. The problem with this approach is that the method can't be private. It has to be protected at the minimum:

public class ClassWithPrivateMethods
{
    public int Add(int firstNumber, int secondNumber)
    {
        return firstNumber + secondNumber;
    }

    protected int Substract(int initialNumber, int numberToSubstract)
    {
        return initialNumber - numberToSubstract;
    }
}

Which means you'll have a test class like this:

private class ClassWithPrivateMethods_TestWrapper : ClassWithPrivateMethods
{
    public new int Substract(int initialNumber, int numberToSubstract)
    {
        return base.Substract(initialNumber, numberToSubstract);
    }
}

And a unit-test like this:

[TestMethod]
public void TestSubstractionInt()
{
    ClassWithPrivateMethods_TestWrapper classToTest = new ClassWithPrivateMethods_TestWrapper();
    int result = classToTest.Substract(10, 5);
    Assert.AreEqual(5, result);
}

I've done this before and it works well. But this way you still can't have private functions.
Enter reflection.
With reflection you can do anything you want to the class. Even invoke private methods! So we'll change the helper class into this:

private class ClassWithPrivateMethods_TestWrapper : ClassWithPrivateMethods
{
    public int Substract(int initialNumber, int numberToSubstract)
    {
       Type t = typeof(ClassWithPrivateMethods);
       return (int)t.InvokeMember("Substract", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod,
       null, this, new object[] { initialNumber, numberToSubstract });
    }
}

Ofcourse you don't really need the wrapper class, you could just as easy put these two lines of reflection stuff inside the unit-test if you wanted. But if you do use the wrapper class, you can just instantiate it and call the "Substract" method for your unit-testing purposes, just like the unit-test above.
This also works for methods that have been overloaded for multiple data types. Suppose we have the following class we want to test:

public class ClassWithPrivateMethods
{
    public int Add(int firstNumber, int secondNumber)
    {
        return firstNumber + secondNumber;
    }

    private int Substract(int initialNumber, int numberToSubstract)
    {
         return initialNumber - numberToSubstract;
    }

    private float Substract(float initialNumber, float numberToSubstract)
    {
        return initialNumber - numberToSubstract;
    }
}

Our wrapper class would look like this:

private class ClassWithPrivateMethods_TestWrapper : ClassWithPrivateMethods
{
    public int Substract(int initialNumber, int numberToSubstract)
    {
        Type t = typeof(ClassWithPrivateMethods);
        return (int)t.InvokeMember("Substract", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod,
        null, this, new object[] { initialNumber, numberToSubstract });
    }

    public float Substract(float initialNumber, float numberToSubstract)
    {
        Type t = typeof(ClassWithPrivateMethods);
        return (float)t.InvokeMember("Substract", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod,
        null, this, new object[] { initialNumber, numberToSubstract });
    }
}

And you can unit-test like this:

[TestMethod]
public void TestAddition()
{
    ClassWithPrivateMethods classToTest = new ClassWithPrivateMethods();
    int result = classToTest.Add(5, 5);
    Assert.AreEqual(10, result);
}

[TestMethod]
public void TestSubstractionInt()
{
    ClassWithPrivateMethods_TestWrapper classToTest = new ClassWithPrivateMethods_TestWrapper();
    int result = classToTest.Substract(10, 5);
    Assert.AreEqual(5, result);
}

[TestMethod]
public void TestSubstractionFloat()
{
    ClassWithPrivateMethods_TestWrapper classToTest = new ClassWithPrivateMethods_TestWrapper();
    float result = classToTest.Substract(10.0f, 2.5f);
    Assert.AreEqual(7.5f, result);
}

No comments: