You are not supposed to test the private methods of a class, but it is occasionally useful. Microsoft used to have a private accessor type but has since deprecated it. This is the way I came up with: Put a pulic wrapper around the methods, and use reflection to call them. Not original, but it works ok.
The reflection class has one main method: RunMethod. I added (ok, started with) a ListMethod as a helper.
namespace UnitTests.Accessors
{
using System;
using System.Reflection;
/// <summary>
/// Accessor helper, uses reflection to call private methods of a class
/// </summary>
class AccessorReflection
{
/// <summary>
/// The BindingFlags flags used to access the class methods
/// BindingFlags.NonPublic will not return any results by itself.
/// As it turns out, combining it with BindingFlags.Instance does the trick.
/// </summary>
private const BindingFlags accessFlags =
BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Static;
/// <summary>
/// Runs the method of the class type.
/// </summary>
/// <param name="objectType">The class type.</param>
/// <param name="methodName">The method to run as a string.</param>
/// <param name="objectInstance">The object instance.</param>
/// <param name="objectParameters">The object method parameters.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentException">There is no method ' +
/// strMethod + ' for type ' + t.ToString() + '.</exception>
public static object RunMethod(Type objectType, string methodName,
object objectInstance, object[] objectParameters)
{
if (String.IsNullOrEmpty(methodName))
{
throw new
ArgumentException("Cannot invoke null or empty method for type " + objectType.ToString() + "'.");
}
MethodInfo m = objectType.GetMethod(methodName, AccessorReflection.accessFlags);
if (m == null)
{
// Method does not exist
throw new ArgumentException("There is no method '" +
methodName + "' for type '" + objectType.ToString() + "'.");
}
return m.Invoke(objectInstance, objectParameters);
}
/// <summary>
/// Lists the methods of the type. Pretty much for debugging
/// </summary>
/// <param name="t">The class type.</param>
public void ListMethods(Type t)
{
Console.WriteLine("***** Methods *****");
MethodInfo[] methodInfo = t.GetMethods(AccessorReflection.accessFlags);
// sort methods by name
Array.Sort(methodInfo,
delegate(MethodInfo methodInfo1, MethodInfo methodInfo2)
{ return methodInfo1.Name.CompareTo(methodInfo2.Name); });
foreach (MethodInfo methodEntry in methodInfo)
{
// Get return type.
string retVal = methodEntry.ReturnType.FullName;
string paramInfo = "( ";
// Get params.
foreach (ParameterInfo parameterInfo in methodEntry.GetParameters())
{
paramInfo += string.Format("{0} {1} ", parameterInfo.ParameterType, parameterInfo.Name);
}
paramInfo += " )";
// Now display the basic method sig.
Console.WriteLine("->{0} {1} {2}", retVal, methodEntry.Name, paramInfo);
}
Console.WriteLine();
}
}
}
{
using System;
using System.Reflection;
/// <summary>
/// Accessor helper, uses reflection to call private methods of a class
/// </summary>
class AccessorReflection
{
/// <summary>
/// The BindingFlags flags used to access the class methods
/// BindingFlags.NonPublic will not return any results by itself.
/// As it turns out, combining it with BindingFlags.Instance does the trick.
/// </summary>
private const BindingFlags accessFlags =
BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Static;
/// <summary>
/// Runs the method of the class type.
/// </summary>
/// <param name="objectType">The class type.</param>
/// <param name="methodName">The method to run as a string.</param>
/// <param name="objectInstance">The object instance.</param>
/// <param name="objectParameters">The object method parameters.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentException">There is no method ' +
/// strMethod + ' for type ' + t.ToString() + '.</exception>
public static object RunMethod(Type objectType, string methodName,
object objectInstance, object[] objectParameters)
{
if (String.IsNullOrEmpty(methodName))
{
throw new
ArgumentException("Cannot invoke null or empty method for type " + objectType.ToString() + "'.");
}
MethodInfo m = objectType.GetMethod(methodName, AccessorReflection.accessFlags);
if (m == null)
{
// Method does not exist
throw new ArgumentException("There is no method '" +
methodName + "' for type '" + objectType.ToString() + "'.");
}
return m.Invoke(objectInstance, objectParameters);
}
/// <summary>
/// Lists the methods of the type. Pretty much for debugging
/// </summary>
/// <param name="t">The class type.</param>
public void ListMethods(Type t)
{
Console.WriteLine("***** Methods *****");
MethodInfo[] methodInfo = t.GetMethods(AccessorReflection.accessFlags);
// sort methods by name
Array.Sort(methodInfo,
delegate(MethodInfo methodInfo1, MethodInfo methodInfo2)
{ return methodInfo1.Name.CompareTo(methodInfo2.Name); });
foreach (MethodInfo methodEntry in methodInfo)
{
// Get return type.
string retVal = methodEntry.ReturnType.FullName;
string paramInfo = "( ";
// Get params.
foreach (ParameterInfo parameterInfo in methodEntry.GetParameters())
{
paramInfo += string.Format("{0} {1} ", parameterInfo.ParameterType, parameterInfo.Name);
}
paramInfo += " )";
// Now display the basic method sig.
Console.WriteLine("->{0} {1} {2}", retVal, methodEntry.Name, paramInfo);
}
Console.WriteLine();
}
}
}
I also added a base abstract class to call it:
namespace UnitTests.Accessors
{
using System;
///
/// Base class to access private methods for testing.
///
/// Object under test
class AccessorBase
{
///
/// Holds the reference to the object under test
///
private T objectInstance;
public AccessorBase(T objectInstance)
{
this.objectInstance = objectInstance;
}
///
/// Executes the private method of the instance
///
/// Method to execute as a string
/// Parameters for the method
/// The result of the method execution
public object RunMethod(string methodName, Object[] parameters)
{
return AccessorReflection.RunMethod(objectInstance.GetType(), methodName, this.objectInstance, parameters);
}
}
}
{
using System;
///
/// Base class to access private methods for testing.
///
/// Object under test
class AccessorBase
{
///
/// Holds the reference to the object under test
///
private T objectInstance;
public AccessorBase(T objectInstance)
{
this.objectInstance = objectInstance;
}
///
/// Executes the private method of the instance
///
/// Method to execute as a string
/// Parameters for the method
/// The result of the method execution
public object RunMethod(string methodName, Object[] parameters)
{
return AccessorReflection.RunMethod(objectInstance.GetType(), methodName, this.objectInstance, parameters);
}
}
}
So now write an accessor class with the public wrappers. My example is dumb but it gets the point across.
class Mine
{
private int count;
public Mine(int count)
{
this.count = count;
}
public void publicMethodNoArgs()
{
this.privateMethodNoArgs();
}
private void privateMethodNoArgs()
{
int joy = 1;
for (int x = 1; x < 5; x++)
{
joy += x;
}
}
private void privateMethodIntArg(int joy)
{
Console.WriteLine("Joy before is {0}", joy);
for (int x = 1; x < 5; x++)
{
joy += x;
}
Console.WriteLine("Joy is now {0}", joy);
}
private int privateMethodStringArg(string name)
{
int result = 1;
return result;
}
}
The accessor class looks like:{
private int count;
public Mine(int count)
{
this.count = count;
}
public void publicMethodNoArgs()
{
this.privateMethodNoArgs();
}
private void privateMethodNoArgs()
{
int joy = 1;
for (int x = 1; x < 5; x++)
{
joy += x;
}
}
private void privateMethodIntArg(int joy)
{
Console.WriteLine("Joy before is {0}", joy);
for (int x = 1; x < 5; x++)
{
joy += x;
}
Console.WriteLine("Joy is now {0}", joy);
}
private int privateMethodStringArg(string name)
{
int result = 1;
return result;
}
}
class Mine_Accessor : AccessorBase<Mine_Accessor>
{
public Mine_Accessor(Mine mineInstance): base(mineInstance)
{
}
public void privateMethodIntArg(int joy)
{
Object[] parms = {joy};
base.RunMethod("privateMethodIntArg", parameters);
}
}
I like to make the public wrapper look like the private method in names and parameters. If there is a return type other than void, add a return and cast the result.
{
public Mine_Accessor(Mine mineInstance): base(mineInstance)
{
}
public void privateMethodIntArg(int joy)
{
Object[] parms = {joy};
base.RunMethod("privateMethodIntArg", parameters);
}
}
No comments:
Post a Comment