我正在构建一个类库,它将有一些公共和私有方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可以用于将来的重构)。
正确的做法是什么?
我正在构建一个类库,它将有一些公共和私有方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可以用于将来的重构)。
正确的做法是什么?
当前回答
一种方法是保护您的方法,并编写一个继承要测试的类的测试fixture。这样,您既没有将方法公开,但是启用了测试。
其他回答
有两种类型的私有方法。静态私有方法和非静态私有方法(实例方法)。下面两篇文章用示例解释了如何对私有方法进行单元测试。
单元测试静态私有方法 单元测试非静态私有方法
在调试模式下构建时,你也可以将其声明为public或internal(使用InternalsVisibleToAttribute):
/// <summary>
/// This Method is private.
/// </summary>
#if DEBUG
public
#else
private
#endif
static string MyPrivateMethod()
{
return "false";
}
它使代码膨胀,但在发布版本中它将是私有的。
在c#中,您可以使用我下面提供的代码。尽管我认为只有在绝对需要的时候才应该进行私有方法的单元测试。我遇到过一些我觉得有必要这样做的案例。下面是我在UnitTestBase类中创建的一些c#方法,我继承了我的UnitTest类(你也可以把它放在一个静态的“助手”类中)。HTH
protected internal static TResult? InvokePrivateInstanceMethod<TType, TResult>(string methodName, object?[]? methodArguments = null, params object?[]? constructorArguments)
{
var classType = typeof(TType);
var instance = Activator.CreateInstance(classType, constructorArguments);
var privateMethodInfo = classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.FirstOrDefault(m => m.IsPrivate &&
m.Name.Equals(methodName, StringComparison.Ordinal) &&
m.ReturnType.Equals(typeof(TResult)));
if (privateMethodInfo is null)
{
throw new MissingMethodException(classType.FullName, methodName);
}
var methodResult = privateMethodInfo.Invoke(instance, methodArguments);
if (methodResult is not null)
{
return (TResult)methodResult;
}
return default;
}
protected internal static async Task<TResult?> InvokePrivateInstanceMethodAsync<TType, TResult>(string methodName, object?[]? methodArguments = null, params object?[]? constructorArguments)
{
var classType = typeof(TType);
var instance = Activator.CreateInstance(classType, constructorArguments);
var privateMethodInfo = classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.FirstOrDefault(m => m.IsPrivate &&
m.Name.Equals(methodName, StringComparison.Ordinal) &&
m.ReturnType.Equals(typeof(Task<TResult>)));
if (privateMethodInfo is null)
{
throw new MissingMethodException(classType.FullName, methodName);
}
var methodResult = privateMethodInfo.Invoke(instance, methodArguments);
if (methodResult is not null)
{
return await (Task<TResult>)methodResult;
}
return default;
}
我很惊讶还没有人说过这一点,但我采用的一个解决方案是在类内部创建一个静态方法来测试自身。这使您可以访问用于测试的所有公共和私有内容。
此外,在脚本语言(具有OO功能,如Python、Ruby和PHP)中,您可以在运行时对文件本身进行测试。确保您的更改不会破坏任何东西的快速方法。这显然为测试所有类提供了可伸缩的解决方案:只需运行所有类即可。(你也可以用void main在其他语言中这样做,它也总是运行它的测试)。
对于任何想要运行私有方法的人来说。这适用于任何单元测试框架,只使用旧的Reflection。
public class ReflectionTools
{
// If the class is non-static
public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
{
Type t = objectUnderTest.GetType();
return t.InvokeMember(method,
BindingFlags.InvokeMethod |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static,
null,
objectUnderTest,
args);
}
// if the class is static
public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
{
MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
foreach(var member in members)
{
if (member.Name == method)
{
return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
}
}
return null;
}
}
然后在你的实际测试中,你可以这样做:
Assert.AreEqual(
ReflectionTools.InvokePrivate(
typeof(StaticClassOfMethod),
"PrivateMethod"),
"Expected Result");
Assert.AreEqual(
ReflectionTools.InvokePrivate(
new ClassOfMethod(),
"PrivateMethod"),
"Expected Result");