您的位置:首页 > 其它

.NET中如何测试Private和Protected方法

2013-10-23 11:31 447 查看
TDD是1)写测试2)写通过这些测试的代码,3)然后重构的实践.在,NET社区中, 这个概念逐渐变得非常流行,这归功于它所增加的质量保证.此时,它很容易测试public方法,但是一个普遍的问题出现了,”我如何测试Protected和private方法呢?”

本文将:

总结”你是否应该测试private方法的争论?”的一些关键点.

创建一些案例,这些案例仍旧是有用的,至少知道怎样测试private和protected方法—不考虑你站在争论的哪一边.

提供方法和可下载的代码示例来展现这些测试技术.

背后的方法

你是否应该测试private方法?

一个Google查询 向你展示了有很多关于使用private方法的争议,更不用说测试他们了.下面这个表概括了一些关于这个话题的正方和反方的普遍意见.

正方

反方

使用private方法

封装private方法提供了封装,对于终端客户来说,它使代码更易使用

重构更容易重构private方法,一位他们永远不会直接被外部的客户端调用,因此,修改签名(Signature)不会影响任何方法调用.

验证 不像public方法那样必须验证所有输入,因为他们被外部调用时,private方法在类中安全调用,不需要同样严格的验证—输入应该在public方法中已经验证了.

测试范围暴露每个方法为public,将在很大程度上增加测试的范围.private方法仅仅使用在开发者如何去使用他们,然而public方法需要测试每种可能,这就需要一个更广阔的测试范围了.

不能重构如果一个类足够复杂,值得使用private方法,那么它需要重构.

隐藏功能性private方法(如果正确设计)提供有用的客户端可以访问的特性,那么任何private方法都值得测试,并且应该真正为public.

测试Private方法

测试控制private方法可以包含复杂的逻辑,并且它可以增加测试控制来直接访问方法,测试它,来代替通过一个public方法间接访问它.

原则单元测试是测试最小的功能代码片断.private方法是功能型代码片断,因此,基于原则,private方法应该是可测试的.

已经覆盖了仅仅只有public接口才可以测试.private已经测试了, 它是通过测试的public方法调用来完成的.

脆弱的代码如果你重构代码,操作private方法,并且如果你有和这些private相关的测试,你同时也需要操作这些测试.

在这些主题的两方,都有明了并且具有经验的人.因此我不打算,也不期望终结”我是否应该测试private方法”的争论.但是对于双方来说,这里仍有价值来知道如何测试他们,即使你认为private不应该被测试.

如果你至少能表现出你可以测试他们,但是你没有这样做(例如,你没有简单的说”不要测试private方法”,因为你不知道如何去测试),你的观点将更加具有说服力.

测试非public方法的选择让你明白在你的小组中,什么真正做的最好.

只要仍有有效的条件,是值得拥有一种方便的方法来测试他们.

好的原则以及不适当的技术

Andrew Hunt a和 David Thomas在他们的书中Pragmatic Unit Testing in C# with NUnit, 解释到,好的单元测试是ATRIP:

自动化(Automatic)

彻底(Thorough )

可重复(Repeatable)

独立(Independent )

专业(Professional)

对于测试private/protected方法来说,有另外三个附加原则:

透明(Transparency) - 不要改变测试下的系统(System Under Test ,SUT),例如,在产品代码中增加包装的方法.

范围(Scope) - 可以在Debug和Release下运行

简单(Simplicity) -最小的开销,因此容易修改,并且非常简单引入最小的风险.

记住这些原则,下面是一些不足的策略.

策略

问题

不要使用任何private方法.

它避免这个问题

使用指示符#if DEBUG ...#endif来包装一个public方法,这个方法然后包装private方法.单元测试现在可以间接访问那些public方法包装的private方法.(这是一种我使用许多次的方法,并且发现它是单调的,不是面向对象的)

只在Debug下工作.

它是一个过程,而不是面向对象.我们需要在产品代码和单元测试中包装单独的方法.

通过增加public方法,会修改SUT.

Public方法使用[
Conditional(
"DEBUG"
)]
属性包装
private方法.


只在Debug下工作.

创建内部方法来访问private方法.然后在public方法包装那些private方法的程序集的其他地方,创建一个公共的测试类.

通过增加内部钩子,以及最后使得private方法在产品中可用,会改编发布代码.

这需要很多额外得编码,因此使脆弱得.

测试Protected方法

Protected方法仅仅对于它得继承类可见,因此,对于测试套件来说并不是立即可见的.例如,激射我们想测试来自from
ClassLibrary1.MyObject
的方法.

protected string MyProtectedMethod(string strInput, int i32Value)
{
return this.Name + ": " + strInput + ", " +
i32Value.ToString();
}


Pragmatic Unit Testing in C# with NUnit一书解释了一个解决方案:创建一个继承自MyObject类的类MyObjectTester,然后创建一个public方法
TestMyProtectedMethod
,
这个方法包装了那个
protected
方法
.
例如
,


public new string TestMyProtectedMethod(string strInput, int i32Value)
{
return base.MyProtectedMethod(strInput,  i32Value);
}


方法很简单,也遵循所有原则:

原则

实现

透明

通过使用继承,并把
MyObjectTester
类放入
UnitTests
程序集中,它不需要增加任何新的代码到产品程序集中.

范围

在本方法中没有任何东西依赖Debug-only技术.

简单

尽管这个方法需要一新的类,以及每个protected 方法的额外public包装方法,但是它是面向对象的,并且使类型安全的.

测试Private方法

测试private方法需要多做有些工作,但是我们仍可以使用System.Reflection来实现.你可以使用反射来动态访问一种类型的方法, 包括实例和静态private方法的方法.注意访问private方法需要ReflectionPermission,但是对于运行在开发机器或者构建服务器上的单元测试来说,这不是问题.

假设我们想测试来自
ClassLibrary1.MyObject
的private方法
MyPrivateMethod
:

private string MyPrivateMethod(string strInput, DateTime dt, double dbl)
{
return this.Name + ": " + strInput + ", " +
dt.ToString() + ", " + dbl.ToString();
}


一个解决方法是创建一个UnitTestUtilities工程,这个工程有一个helper类通过反射来调用测试方法.例如,供下载的解决方案在
UnitTestUtilities.Helper
中有如下方法:

public static object RunStaticMethod(System.Type t, string strMethod,
object [] aobjParams)
{
BindingFlags eFlags =
BindingFlags.Static | BindingFlags.Public |
BindingFlags.NonPublic;
return RunMethod(t, strMethod,
null, aobjParams, eFlags);
} //end of method
public static object RunInstanceMethod(System.Type t, string strMethod,
object objInstance, object [] aobjParams)
{
BindingFlags eFlags = BindingFlags.Instance | BindingFlags.Public |      BindingFlags.NonPublic;
return RunMethod(t, strMethod,
objInstance, aobjParams, eFlags);
} //end of method18private static object RunMethod(System.Type t, string
strMethod, object objInstance, object [] aobjParams, BindingFlags eFlags)
{
MethodInfo m;
try
{
m = t.GetMethod(strMethod, eFlags);
if (m == null)
{
throw new ArgumentException("There is no method '" +               strMethod + "' for type '" + t.ToString() + "'.");
}

object objRet = m.Invoke(objInstance, aobjParams);
return objRet;
}
catch
{
throw;
}
} //end of method


Private方法
RunMethod
带有一些必要的参数,这些参数是反射需要用来调用一个方法,然后返回值的.它有两个public方法
RunStaticMethod
RunInstanceMethod
来为静态和实例方法分别包装这.

看看
RunMethod
,它首先得到类型的
MethodInfo.
因为我们期望它仅为已经存在的方法调用
.
一个空的方法触发一个
Exception
.
一旦我们有
MethodInfo
,我们就可以调用实例化对象提供的方法(static 方法为null)以及参数数组.

我们可以在一个NUnit测试中像下面使用这个Utility:

[Test]
public void TestPrivateInstanceMethod()
{
string strExpected = "MyName: Hello, 5/24/2004
12:00:00 AM, 2.1";

ClassLibrary1.MyObject objInstance
= new MyObject("MyName");

object obj =
UnitTestUtilities.Helper.RunInstanceMethod(
typeof(ClassLibrary1.MyObject), "MyPrivateMethod",
objInstance, new object[3] {"Hello",
new DateTime(2004,05,24), 2.1});

string strActual = Convert.ToString(obj);
Assert.AreEqual(strExpected,strActual);18}


原则

实现

透明

我们仅创建的多余代码;
UnitTestUtilities
,它没有带到产品中.

范围

在本方法中没有任何东西依赖Debug-only技术.

简单

Because the method is being dynamically called, the parameters aren't checked at compile time.本方法可以通过一个简单的调用来调用任何方法.一旦你有
UnitTestUtilities
,你唯一要完成的是为
RunInstanceMethod
or
RunStaticMethod
创建正确的参数(方法名,数据类型,等…),因为方法动态的被调用,参数在编译的时候不会得到检查.

总结

关于是否应该测试private方法仍有争论,但是我们有能力去测试他们.我们可以使用继承创 建一个继承类
TesterClass
来测试protected方法.这个继承类包装了其基类的 protected方法为public.我们可以是哦女冠反射来测试private方法,它能够抽象 到一个
UnitTestUtility
helper类.这些技术都能帮助你改进测试覆盖面.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: