我正在构建一个类库,它将有一些公共和私有方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可以用于将来的重构)。
正确的做法是什么?
我正在构建一个类库,它将有一些公共和私有方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可以用于将来的重构)。
正确的做法是什么?
当前回答
MS Test内置了一个很好的特性,通过创建一个名为VSCodeGenAccessors的文件,可以在项目中使用私有成员和方法
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class BaseAccessor
{
protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;
protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
{
m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
}
protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
:
this(null, type)
{
}
internal virtual object Target
{
get
{
return m_privateObject.Target;
}
}
public override string ToString()
{
return this.Target.ToString();
}
public override bool Equals(object obj)
{
if (typeof(BaseAccessor).IsInstanceOfType(obj))
{
obj = ((BaseAccessor)(obj)).Target;
}
return this.Target.Equals(obj);
}
public override int GetHashCode()
{
return this.Target.GetHashCode();
}
}
使用派生自BaseAccessor的类
如
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{
protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));
internal SomeClassAccessor(global::Namespace.Someclass target)
: base(target, m_privateType)
{
}
internal static string STATIC_STRING
{
get
{
string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
return ret;
}
set
{
m_privateType.SetStaticField("STATIC_STRING", value);
}
}
internal int memberVar {
get
{
int ret = ((int)(m_privateObject.GetField("memberVar")));
return ret;
}
set
{
m_privateObject.SetField("memberVar", value);
}
}
internal int PrivateMethodName(int paramName)
{
object[] args = new object[] {
paramName};
int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
typeof(int)}, args)));
return ret;
}
其他回答
如果您想对私有方法进行单元测试,那么可能会出现一些错误。单元测试(一般来说)用于测试类的接口,即类的公共(和受保护)方法。你当然可以“破解”一个解决方案(即使只是通过公开方法),但你可能还想考虑:
如果您想测试的方法确实值得测试,那么将它移到自己的类中可能是值得的。 向调用私有方法的公共方法添加更多测试,测试私有方法的功能。(正如评论者所指出的,只有当这些私有方法的功能确实是公共接口的一部分时,才应该这样做。如果它们实际上执行对用户隐藏的功能(即单元测试),这可能是不好的)。
在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;
}
由于某些原因,私有类型、内部变量和私有成员是这样的,通常你不想直接打乱它们。如果您这样做了,很可能稍后会崩溃,因为不能保证创建这些程序集的人会保留私有/内部实现。
但是,有时,在对编译或第三方程序集进行一些hack /探索时,我自己最终想要初始化一个私有类或具有私有或内部构造函数的类。或者,有时,在处理无法更改的预编译遗留库时——我最终会针对私有方法编写一些测试。
因此诞生了AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html -它是一个快速的包装类,使用c# 4.0的动态特性和反射可以使工作变得容易。
您可以创建内部/私有类型,例如
//Note that the wrapper is dynamic
dynamic wrapper = AccessPrivateWrapper.FromType
(typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");
//Access the private members
wrapper.PrivateMethodInPrivateClass();
MS Test内置了一个很好的特性,通过创建一个名为VSCodeGenAccessors的文件,可以在项目中使用私有成员和方法
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class BaseAccessor
{
protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;
protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
{
m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
}
protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
:
this(null, type)
{
}
internal virtual object Target
{
get
{
return m_privateObject.Target;
}
}
public override string ToString()
{
return this.Target.ToString();
}
public override bool Equals(object obj)
{
if (typeof(BaseAccessor).IsInstanceOfType(obj))
{
obj = ((BaseAccessor)(obj)).Target;
}
return this.Target.Equals(obj);
}
public override int GetHashCode()
{
return this.Target.GetHashCode();
}
}
使用派生自BaseAccessor的类
如
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{
protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));
internal SomeClassAccessor(global::Namespace.Someclass target)
: base(target, m_privateType)
{
}
internal static string STATIC_STRING
{
get
{
string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
return ret;
}
set
{
m_privateType.SetStaticField("STATIC_STRING", value);
}
}
internal int memberVar {
get
{
int ret = ((int)(m_privateObject.GetField("memberVar")));
return ret;
}
set
{
m_privateObject.SetField("memberVar", value);
}
}
internal int PrivateMethodName(int paramName)
{
object[] args = new object[] {
paramName};
int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
typeof(int)}, args)));
return ret;
}
我还使用了InternalsVisibleToAttribute方法。值得一提的是,如果你为了达到这个目的而将你之前的私有方法变成内部方法,那么也许它们不应该成为直接单元测试的对象。
毕竟,您是在测试类的行为,而不是它的具体实现——您可以在不更改前者的情况下更改后者,并且您的测试仍然应该通过。