Eri*_*sky 470 .net tdd unit-testing private
我正在构建一个具有一些公共和私有方法的类库.我希望能够对私有方法进行单元测试(主要是在开发时,但它也可能对将来的重构有用).
这样做的正确方法是什么?
Jer*_*ans 344
如果要对私有方法进行单元测试,可能会出现问题.单元测试(一般来说)用于测试类的接口,即其公共(和受保护)方法.你当然可以"破解"这个解决方案(即使只是通过公开方法),但你可能还想考虑:
TcK*_*cKs 120
如果您使用的是.net,则应使用InternalsVisibleToAttribute.
Sev*_*ven 117
测试私有方法可能没用.但是,我有时也喜欢从测试方法中调用私有方法.大多数情况下为了防止代码重复生成测试数据...
Microsoft为此提供了两种机制:
访问器
但是,当涉及原始类的接口的更改时,该机制有时有点棘手.所以,大多数时候我都避免使用它.
PrivateObject类 另一种方法是使用Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject
// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );
// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );
// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
Run Code Online (Sandbox Code Playgroud)
Dar*_*ank 78
我不同意"你应该只对测试外部接口感兴趣"的理念.这有点像说汽车维修店应该只进行测试,看车轮是否转动.是的,最终我对外部行为感兴趣,但我喜欢我自己的私人内部测试更具体和更重要.是的,如果我重构,我可能不得不改变一些测试,但除非它是一个巨大的重构,我只需要改变一些,而其他(未改变的)内部测试仍然有效的事实是一个很好的指标,重构成功了.
您可以尝试仅使用公共接口覆盖所有内部情况,理论上可以通过使用公共接口完全测试每个内部方法(或至少每个重要的方法),但您可能必须站在头上才能实现这个以及通过公共接口运行的测试用例与他们设计用于测试的解决方案的内部部分之间的联系可能很难或无法辨别.有针对性的,确保内部机器正常工作的个别测试非常值得重构的小测试变化 - 至少这是我的经验.如果你必须对每次重构的测试做出巨大的改变,那么也许这没有意义,但在这种情况下,也许你应该完全重新考虑你的设计.
Jas*_*son 50
在极少数情况下我想测试私有函数,我通常将它们修改为受保护,而我已经编写了一个带有公共包装函数的子类.
班级:
...
protected void APrivateFunction()
{
...
}
...
Run Code Online (Sandbox Code Playgroud)
用于测试的子类:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
Run Code Online (Sandbox Code Playgroud)
Big*_*una 21
I think a more fundamental question should be asked is that why are you trying to test the private method in the first place. That is a code smell that you're trying to test the private method through that class' public interface whereas that method is private for a reason as it's an implementation detail. One should only be concerned with the behaviour of the public interface not on how it's implemented under the covers.
If I want to test the behaviour of the private method, by using common refactorings, I can extract its code into another class (maybe with package level visibility so ensure it's not part of a public API). I can then test its behaviour in isolation.
The product of the refactoring means that private method is now a separate class that has become a collaborator to the original class. Its behaviour will have become well understood via its own unit tests.
I can then mock its behaviour when I try to test the original class so that I can then concentrate on test the behaviour of that class' public interface rather than having to test a combinatorial explosion of the public interface and the behaviour of all its private methods.
我认为这类似于驾驶汽车.当我驾驶汽车时,我没有驾驶发动机罩,所以我可以看到发动机正在工作.我依靠汽车提供的接口,即转速计和速度表来了解发动机是否工作.当我按下油门踏板时,我依靠汽车实际移动的事实.如果我想测试引擎,我可以单独检查它.:d
当然,如果您有遗留应用程序,直接测试私有方法可能是最后的手段,但我希望重构遗留代码以实现更好的测试.Michael Feathers写了一本关于这个主题的好书.http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052
ama*_*int 17
私人类型,内部和私人成员是由于某种原因,并且通常你不想直接搞砸它们.如果你这样做,很可能会在以后破解,因为无法保证创建这些程序集的人将保留私有/内部实现.
但是,有时候,在对编译或第三方程序集进行一些黑客攻击/探索时,我自己最终想要使用私有或内部构造函数初始化私有类或类.或者,有时,在处理我无法更改的预编译遗留库时 - 我最终会针对私有方法编写一些测试.
因此诞生了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();
Run Code Online (Sandbox Code Playgroud)
Unk*_*own 12
那么你可以用两种方式对私有方法进行单元测试
你可以创建PrivateObject
类的实例,语法如下
PrivateObject obj= new PrivateObject(PrivateClass);
//now with this obj you can call the private method of PrivateCalss.
obj.PrivateMethod("Parameters");
Run Code Online (Sandbox Code Playgroud)你可以使用反射.
PrivateClass obj = new PrivateClass(); // Class containing private obj
Type t = typeof(PrivateClass);
var x = t.InvokeMember("PrivateFunc",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Instance, null, obj, new object[] { 5 });
Run Code Online (Sandbox Code Playgroud)phi*_*red 10
我还使用了InternalsVisibleToAttribute方法.值得一提的是,如果你为了实现这一目标而让你以前的私人方法内部感到不舒服,那么也许他们不应该成为直接单元测试的主题.
毕竟,你正在测试你的类的行为,而不是它的具体实现 - 你可以改变后者而不改变前者,你的测试仍然应该通过.
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();
}
}
Run Code Online (Sandbox Code Playgroud)
使用派生自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;
}
Run Code Online (Sandbox Code Playgroud)
在CodeProject上,有一篇文章简要讨论了测试私有方法的优缺点.然后它提供了一些访问私有方法的反射代码(类似于Marcus上面提供的代码.)我在样本中发现的唯一问题是代码没有考虑重载方法.
你可以在这里找到这篇文章:
http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx
对于任何想要运行私有方法而没有所有麻烦和混乱的人。这适用于任何只使用旧式反射的单元测试框架。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后在实际测试中,你可以这样做:
Assert.AreEqual(
ReflectionTools.InvokePrivate(
typeof(StaticClassOfMethod),
"PrivateMethod"),
"Expected Result");
Assert.AreEqual(
ReflectionTools.InvokePrivate(
new ClassOfMethod(),
"PrivateMethod"),
"Expected Result");
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
183138 次 |
最近记录: |