我正在构建一个类库,它将有一些公共和私有方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可以用于将来的重构)。
正确的做法是什么?
我正在构建一个类库,它将有一些公共和私有方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可以用于将来的重构)。
正确的做法是什么?
当前回答
我倾向于不使用编译器指令,因为它们很快就会把事情弄得乱七八糟。如果你真的需要它们,一种缓解它的方法是把它们放在一个partial类中,并让你的构建在制作产品版本时忽略那个.cs文件。
其他回答
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;
}
在我看来,你应该只对类的公共API进行单元测试。
为了对方法进行单元测试,将其设为公共,会破坏封装,暴露实现细节。
一个好的公共API解决了客户端代码的直接目标,并且完全解决了这个目标。
这里有一个例子,首先是方法签名:
private string[] SplitInternal()
{
return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
.Cast<Match>()
.Select(m => m.Value)
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
}
下面是测试:
/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
object[] values = new object[] { 2, "Martin" };
XPathString xp = new XPathString(path, values);
PrivateObject param0 = new PrivateObject(xp);
XPathString_Accessor target = new XPathString_Accessor(param0);
string[] expected = new string[] {
"pair[path/to/@Key={0}]",
"Items",
"Item[Name={1}]",
"Date"
};
string[] actual;
actual = target.SplitInternal();
CollectionAssert.AreEqual(expected, actual);
}
我还使用了InternalsVisibleToAttribute方法。值得一提的是,如果你为了达到这个目的而将你之前的私有方法变成内部方法,那么也许它们不应该成为直接单元测试的对象。
毕竟,您是在测试类的行为,而不是它的具体实现——您可以在不更改前者的情况下更改后者,并且您的测试仍然应该通过。
还请注意,internalsvisibletoattribute有一个要求,您的程序集必须是强命名的,如果您在一个以前没有这个要求的解决方案中工作,这就会产生它自己的一组问题。我使用访问器来测试私有方法。请看这个问题的例子。