TDD是1)写测试2)写通过这些测试的代码,3)然后重构的实践.在,NET社区中, 这个概念逐渐变得非常流行,这归功于它所增加的质量保证.此时,它很容易测试public方法,但是一个普遍的问题出现了,”我如何测试Protected和private方法呢?”
本文将:
一个Google查询 向你展示了有很多关于使用private方法的争议,更不用说测试他们了.下面这个表概括了一些关于这个话题的正方和反方的普遍意见.
正方
反方
使用private方法
测试Private方法
在这些主题的两方,都有明了并且具有经验的人.因此我不打算,也不期望终结”我是否应该测试private方法”的争论.但是对于双方来说,这里仍有价值来知道如何测试他们,即使你认为private不应该被测试.
Andrew Hunt a和 David Thomas在他们的书中Pragmatic Unit Testing in C# with NUnit, 解释到,好的单元测试是ATRIP:
对于测试private/protected方法来说,有另外三个附加原则:
记住这些原则,下面是一些不足的策略.
策略
问题
不要使用任何private方法.
使用指示符#if DEBUG ... #endif来包装一个public方法,这个方法然后包装private方法.单元测试现在可以间接访问那些public方法包装的private方法.(这是一种我使用许多次的方法,并且发现它是单调的,不是面向对象的)
Public方法使用[Conditional(
"DEBUG")]
属性包装
private方法.
创建内部方法来访问private方法.然后在public方法包装那些private方法的程序集的其他地方,创建一个公共的测试类.
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方法需要多做有些工作,但是我们仍可以使用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类.这些技术都能帮助你改进测试覆盖面.