转载
刚刚试着用nhibernate做些例子,重装了下系统后发现数据处理突然变的很慢,这才想到使用NUnit搞下单元测试.
http://www.nunit.org/去下载了2个文件:
1.NUnit-2.2.8-net-1.1.msi
2.NUnit-2.2.8-net-1.1.zip
如果你不想安装,就直接使用第二个,但是没办法在vs2003上直接使用,所以最好安装下.
1. TDD的简介
首先什么是TDD呢?Kent Beck在他的<<测试驱动开发 >>(Addison-Wesley Professional,2003)一书中,使用下面2个原则来定义TDD:
· 除非你有一个失败的自动测试,永远不要写一单行代码.
· 阻止重复
我想第一个原则是显而易见的.在没有失败的自动测试下就不要写代码.因为测试是嵌入在代码必须满足的需求中.如果没有需求,就没有必要实现任何东西.所以这个原则阻止我们去实现那些没有测试和在解决方案中不需要的功能.
第二个原则说明了在一个程序中,不应该包含重复的代码.如果代码重复,我想这就是不好的软件设计的象征.随着时间的流逝,它会对程序造成不一致的问题,并且使代码变非常混乱 ,因为我们时常不会记得重复代码的位置.如果发现代码重复,我想我们应该立即删除代码重复.其实这就涉及到重构了.在这里我就不多讲了.
一般来说,测试分为2种类型,一是程序员自己的测试,另外一种是客户的测试.关于客户测试,我推荐一个FIT的框架,非常不错。在这里,我们讲的TDD就是程序员测试.那么什么是程序员测试呢?我认为就是我们常说的单元测试.既然是单元测试,在.NET里势必会用到某些工具,目前最著名恐怕就是我即将介绍的NUnit了,
2.NUnit的介绍
NUnit是一个单元测试框架,专门针对于.NET来写的.其实在前面有JUnit(Java),CPPUnit(C++),他们都是xUnit的一员.最初,它是从JUnit而来.现在的版本是2.2.接下来我所用的都是基于这个版本.
NUnit最初是由James W. Newkirk, Alexei A. Vorontsov 和Philip A. Craig, 后来开发团队逐渐庞大起来.在开发过程中, Kent Beck 和Erich Gamma2位牛人也提供了许多帮助.看来对于NUnit还真是下了一番力气了.J
NUnit是xUnit家族种的第4个主打产品,完全由C#语言来编写,并且编写时充分利用了许多.NET的特性,比如反射,客户属性等等.
最重要的一点是它适合于所有.NET语言.
如果你还没有下载,可以到http://www.nunit.org/去下载.
2.1 NUnit的介绍
Ok,下面正式讲解NUnit.在讲解之前,看看几张图片:
图1 NUnit运行的效果
图2 NUnit运行的另外一个效果
从中我们可以非常容易发现,右边是个状态条,图1是红色的,图2是绿色的.为什么会这样呢?因为如果所有测试案例运行成功,就为绿色,反之如果有一个不成功,则为红色,但也有黄色的.左面的工作域内则是我们写的每一个单元测试.
通过上面的图片,我想你对NUnit有个总的了解了.
接下来还是分为2个部分,一是NUnit的布局,另外一部分就是它的核心概念.
首先熟悉一下NUnit GUI的布局.
让我们更进一步看一下测试运行器窗口的布局。在右边面板的中间,可以看到测试进度条。进度条的颜色反映了测试执行的状态:
绿色 描述目前所执行的测试都通过
黄色 意味某些测试忽略,但是这里没有失败
红色 表示有失败
底部的状态条表示下面的状态:
状态.说明了现在运行测试的状态。当所有测试完成时,状态变为
Completed.运行测试中,状态是
Running: <
test-name> (<
test-name>是正在运行的测试名称)。
Test Cases说明加载的程序集中测试案例的总个数。这也是测试树里叶子节点的个数。
Tests Run 已经完成的测试个数。
Failures 到目前为止,所有测试中失败的个数.
Time 显示运行测试时间(以秒计)
File主菜单有以下内容:
New Project允许你创建一个新工程。工程是一个测试程序集的集合。这种机制让你组织多个测试程序集,并把他们作为一个组对待。
Open 加载一个新的测试程序集,或一个以前保存的NUnit工程文件。
Close关闭现在加载的测试程序集或现在加载的NUnit工程。
Save 保存现在的Nunit工程到一个文件。如果正工作单个程序集,本菜单项允许你创建一个新的NUnit工程,并把它保存在文件里。
Save As允许你将现有NUnit工程作为一个文件保存。
Reload 强制重载现有测试程序集或NUnit工程。NUnit-Gui自动监测现加载的测试程序集的变化。
当程序集变化时,测试运行器重新加载测试程序集。(当测试正运行时,现在加载的测试程序集不会重新加载。在测试运行之间测试程序集仅可以重新加载。一个忠告:如果测试程序集依赖另外一个程序集,测试运行器不会观察任何依赖的程序集。对测试运行器来说,强制一个重载使全部依赖的程序集变化可见。
Recent Files 说明5个最近在NUnit中加载的测试程序集或NUnit工程(这个列表在Windows注册表,由每个用户维护,因此如果你共享你的PC,你仅看到你的测试)。最近程序集的数量可以使用Options菜单项修改,可以访问Tool主菜单。
Exit退出。
View菜单有以下内容:
Expand一层层扩展现在树中所选节点
Collapse 折叠现在树中选择的节点
Expand All递归扩展树中所选节点后的所有节点
Collapse All递归折叠树中所选节点后的所有节点
Expand Fixtures扩展树中所有代表测试fixture的节点。
Collapse Fixtures 折叠树中所有代表测试fixture的节点。
Properties 显示树中现所选节点的属性。
Tools 菜单由这些项:
Save Results as XML作为一XML文件保存运行测试的结果。
Options让你定制NUnit的行为。
现在看看右边,你已经熟悉Run按钮和进度条。这里还有一个紧跟Run按钮的Stop按钮:点击这个按钮会终止执行正运行的测试。进度条下面是一个文本窗口,在它上方,由以下4个标签:
Errors and Failures 窗口显示失败的测试。在我们的例子里,这个窗口是空。
Tests Not Run 窗口显示没有得到执行的测试。
Console.Error 窗口显示运行测试产生的错误消息。这些此消息是应用程序代码使用
Console.Error输出流可以输出的。
Console.Out窗口显示运行测试打印到
Console.Error输出流的文本消息。
2.2 一些常用属性
接下来,我将讲述这个框架如何使用.同时也涉及到一些非常重要的概念,我想其客户属性是非常重要的.在NUnit里,有以下几种属性:
下面我将对每种属性一一讲解.
TestFixtureAttribute
本属性标记一个类包含测试,当然setup和teardown方法可有可无.(关于setup 和teardown方法在后面介绍)
做为一个测试的类,这个类还有一些限制
- 必须是Public,否则NUnit看不到它的存在.
- 它必须有一个缺省的构造函数,否则是NUnit不会构造它.
- 构造函数应该没有任何副作用,因为NUnit在运行时经常会构造这个类多次,如果要是构造函数要什么副作用的话,那不是乱了.
举个例子
1
using
System;
2
using
NUnit.Framework;
3
namespace
MyTest.Tests
4

{
5
6
[TestFixture]
7
public class PriceFixture
8
{
9
// 
10
}
11
}
12
TestAttribute
Test属性用来标记一个类(已经标记为TestFixture)的某个方法是可以测试的.为了和先前的版本向后兼容,头4个字符(“test”)忽略大小写.(参看http://nunit.org/test.html)
这个测试方法可以定义为:
public
void
MethodName()
从上面可以看出,这个方法没有任何参数,其实
测试方法必须没有参数.如果我们定义方法不对的话,这个方法不会出现在测试方法列表中.也就是说在NUnit的界面左边的工作域内,看不到这个方法.
还有一点就是这个方法不返回任何参数,并且必须为Public.
例如:
1
using
System;
2
using
NUnit.Framework;
3

4
namespace
MyTest.Tests
5

{
6
[TestFixture]
7
public class SuccessTests
8
{
9
[Test] public void Test1()
10
{ /**//*
*/ }
11
}
12
}
13

14
一般来说,有了上面两个属性,你可以做基本的事情了.
另外,我们再对如何进行比较做一个描述。
在NUnit中,用Assert(断言)进行比较,Assert是一个类,它包括以下方法:AreEqual,AreSame,Equals, Fail,Ignore,IsFalse,IsNotNull,具体请参看NUnit的文档。
3.如何在.NET中应用NUnit
我将举个例子,一步一步演示如何去使用NUnit.
第1步.为测试代码创建一个Visual Studio工程。
在Microsoft Visual Studio .NET中,让我们开始创建一个新的工程。选择Visual C#工程作为工程类型,Class Library作为模板。将工程命名为
NUnitQuickStart.图4-1是一个描述本步骤的Visual Studio .NET。
图 4-1: 创建第一个NUnit工程
第2步.增加一个NUnit框架引用
在Microsoft Visual Studio .NET里创建这个例子时,你需要增加一个
nunit.framework.dll引用,如下:
在Solution Explorer右击引用,然后选择增加引用
nunit.framework组件,在Add Reference对话框中按Select和OK按钮。
图 4-2: 增加一个
nunit.framework.dll 引用到工程
第3步.为工程加一个类.
为工程加一个
NumbersFixture类。这里是这个例子的代码。
1
using
System;
2
using
NUnit.Framework;
3
4
namespace
NUnitQuickStart
5

{
6
[TestFixture]
7
public class NumersFixture
8
{
9
[Test]
10
public void AddTwoNumbers()
11
{
12
int a=1;
13
int b=2;
14
int sum=a+b;
15
Assert.AreEqual(sum,3);
16
}
17
}
18
}
19
第4步.建立你的Visual Studio 工程,使用NUnit-Gui测试
从程序->NUnit2.2打开NUnit-gui,加载本本工程编译的程序集.
为了在Visual Studio .NET中自动运行NUnit-Gui,你需要建立NUnit-Gui作为你的启动程序:
在 Solution Explorer里右击你的NunitQuickStart工程。
在弹出菜单中选择属性。
在显示的对话框的左面,点击Configuration Properties夹
选择出现在Configuration Properties夹下的Debugging。
在属性框右边的Start Action部分,选择下拉框的Program作为Debug Mode值。
按Apply按钮
设置nunit-gui.exe
作为Start Application。,你既可以键入nunit-gui.exe的全路径,也可使用浏览按钮来指向它。
图 4-3:
将NUnit-Gui 作为工程的测试运行器
第5步.编译运行测试.
现在编译solution。成功编译后,开始应用程序。NUnit-Gui测试运行器出现。当你第一次开始NUnit-Gui,它打开时没有测试加载。从File菜单选择Oprn,浏览
NUnitQuickStart.
dll的路径。当你加载了测试的程序集,测试运行器为加载的程序集的测试产生一个可见的表现。在例子中,测试程序集仅有一个测试,测试程序集的结构如图4-4所示:
图 4-4: 测试程序集的测试在 NUnit-Gui中的视图
按Run按钮。树的节点变为绿色,而且测试运行器窗口上的进度条变绿,绿色代表成功通过。
4.其他的一些核心概念
上面的例子介绍了基本的NUnit特性和功能.
TestFixture,
Test, 和
Assert是3个最基本的特征,我们可以用这些特性进行程序员测试了.但是有的时候,你觉得这3个远远不够,比如有的时候打开一个数据库连接多次,有没有只让它打开一次的方法呢?如果我想把测试分类,应该怎样实现呢?如果我想忽略某些测试,又应该如何去完成呢?不用担心,NUnit已经有这样的功能了.
下面我们一一作出回答.
SetUp/TearDown 属性
在早期给的test fixture定义里,我们说test fixture的测试是一组常规运行时资源.在测试完成之后,或是在测试执行种,或是释放或清除之前,这些常规运行时资源在一确定的方式上可能需要获取和初始化.NUnit使用2个额外的属性:
SetUp 和
TearDown,就支持这种常规的初始化/清除.我们上面的例子来描述这个功能.让我们增加乘法.
1
using
System;
2
using
NUnit.Framework;
3
4
namespace
NUnitQuickStart
5

{
6
[TestFixture]
7
public class NumersFixture
8
{
9
[Test]
10
public void AddTwoNumbers()
11
{
12
int a=1;
13
int b=2;
14
int sum=a+b;
15
Assert.AreEqual(sum,3);
16
}
17
[Test]
18
public void MultiplyTwoNumbers()
19
{
20
int a = 1;
21
int b = 2;
22
int product = a * b;
23
Assert.AreEqual(2, product);
24
}
25
26
}
27
}
28
我们仔细一看,不对,有重复的代码,如何去除重复的代码呢?我们可以提取这些代码到一个独立的方法,然后标志这个方法为
SetUp 属性,这样2个测试方法可以共享对操作数的初始化了,这里是改动后的代码:
1
using
System;
2
using
NUnit.Framework;
3
4
namespace
NUnitQuickStart
5

{
6
[TestFixture]
7
public class NumersFixture
8
{
9
private int a;
10
private int b;
11
[SetUp]
12
public void InitializeOperands()
13
{
14
a = 1;
15
b = 2;
16
}
17
18
[Test]
19
public void AddTwoNumbers()
20
{
21
int sum=a+b;
22
Assert.AreEqual(sum,3);
23
}
24
[Test]
25
public void MultiplyTwoNumbers()
26
{
27
int product = a * b;
28
Assert.AreEqual(2, product);
29
}
30
31
}
32
}
33
这样NUnit将在执行每个测试前执行标记SetUp属性的方法.在本例中就是执行InitializeOperands()方法.记住,这里这个方法必须为public,不然就会有以下错误:Invalid Setup or TearDown method signature
ExpectedException
这里是一个验证这个假设的测试.有的时候,我们知道某些操作会有异常出现,例如, 在实例中增加除法,某个操作被0除,抛出的异常和.NET文档描述的一样.参看以下源代码.
1
[Test]
2
[ExpectedException(
typeof
(DivideByZeroException))]
3
public
void
DivideByZero()
4

{
5
int zero = 0;
6
int infinity = a/zero;
7
Assert.Fail("Should have gotten an exception");
8
}
9
除了[Test]属性之外,
DivideByZero方法有另外一个客户属性:
ExpectedException.在这个属性里,你可以在执行过程中捕获你期望的异常类型,例如在本例就是DivideByZeroException.如果这个方法在没有抛出期望异常的情况下完成了,这个测试失败.使用这个属性帮助我们写程序员测试验证边界条件(Boundary Conditions).
Ignore 属性
由于种种原因,有一些测试我们不想运行.当然,这些原因可能包括你认为这个测试还没有完成,这个测试正在重构之中,这个测试的需求不是太明确.但你有不想破坏测试,不然进度条可是红色的哟.怎么办?使用
Ignore属性.你可以保持测试,但又不运行它们.让我们标记
MultiplyTwoNumbers测试方法为
Ignore属性:
1
[Test]
2
[Ignore(
"
Multiplication is ignored
"
)]
3
public
void
MultiplyTwoNumbers()
4

{
5
int product = a * b;
6
Assert.AreEqual(2, product);
7
}
运行测试,现在产生了下面的输出(在图5-1显示):
图 5-1: 在一个程序员测试中使用 Ignore属性
Ignore属性可以附加到一个独立的测试方法,也可以附加到整个测试类(TestFixture).如果
Ignore属性附加到
TestFixture,所有在fixture的测试都被忽略.
TestFixtureSetUp/TestFixtureTearDown
有时,一组测试需要的资源太昂贵.例如,数据库连接可能是一个关键资源,在一个test fixture的每个测试中,打开/关闭数据库连接可能非常慢.这就是我在开始提到的问题.如何解决?NUnit有一对类似于前面讨论的
SetUp/
TearDown的属性:
TestFixtureSetUp/
TestFixtureTearDown.正如他们名字表明的一样,这些属性用来标记为整个test fixture初始化/释放资源方法一次的方法.
例如,如果你想为所有test fixture的测试共享相同的数据库连接对象,我们可以写一个打开数据库连接的方法,标记为
TestFixtureSetUp属性,编写另外一个关闭数据库连接的方法,标记为
TestFixtureTearDown属性.这里是描述这个的例子.
1
using
NUnit.Framework;
2

3
[TestFixture]
4
public
class
DatabaseFixture
5

{
6
[TestFixtureSetUp]
7
public void OpenConnection()
8
{
9
//open the connection to the database
10
}
11
12
[TestFixtureTearDown]
13
public void CloseConnection()
14
{
15
//close the connection to the database
16
}
17
18
[SetUp]
19
public void CreateDatabaseObjects()
20
{
21
//insert the records into the database table
22
}
23
24
[TearDown]
25
public void DeleteDatabaseObjects()
26
{
27
//remove the inserted records from the database table
28
}
29
30
[Test]
31
public void ReadOneObject()
32
{
33
//load one record using the open database connection
34
}
35
36
[Test]
37
public void ReadManyObjects()
38
{
39
//load many records using the open database connection
40
}
41
}
42

43
Test Suite
Test Suite是test case或其他test suite的集合. 合成(Composite)
,模式描述了test case和test suite之间的关系.
参考来自NUnit的关于Suite的代码
Suite Attribute
1
namespace
NUnit.Tests
2

{
3
using System;
4
using NUnit.Framework;
5
6
7
8
public class AllTests
9
{
10
[Suite]
11
public static TestSuite Suite
12
{
13
get
14
{
15
TestSuite suite = new TestSuite("All Tests");
16
suite.Add(new OneTestCase());
17
suite.Add(new Assemblies.AssemblyTests());
18
suite.Add(new AssertionTest());
19
return suite;
20
}
21
}
22
}
23
}
24
Category属性
对于测试来说,你有的时候需要将之分类,此属性正好就是用来解决这个问题的。
你可以选择你需要运行的测试类目录,也可以选择除了这些目录之外的测试都可以运行。在命令行环境里 /include 和/exclude来实现。在GUI环境下,就更简单了,选择左边工作域里的Catagories Tab,选择Add和Remove既可以了。
在上面的例子上做了一些改善,代码如下:
1
using
System;
2
using
NUnit.Framework;
3
4
namespace
NUnitQuickStart
5

{
6
[TestFixture]
7
public class NumersFixture
8
{
9
private int a;
10
private int b;
11
[SetUp]
12
public void InitializeOperands()
13
{
14
a = 1;
15
b = 2;
16
}
17
18
[Test]
19
[Category("Numbers")]
20
public void AddTwoNumbers()
21
{
22
int sum=a+b;
23
Assert.AreEqual(sum,3);
24
}
25
26
[Test]
27
[Category("Exception")]
28
[ExpectedException(typeof(DivideByZeroException))]
29
public void DivideByZero()
30
{
31
int zero = 0;
32
int infinity = a/zero;
33
Assert.Fail("Should have gotten an exception");
34
}
35
[Test]
36
[Ignore("Multiplication is ignored")]
37
[Category("Numbers")]
38
public void MultiplyTwoNumbers()
39
{
40
int product = a * b;
41
Assert.AreEqual(2, product);
42
}
43
44
}
45
NUnit-GUI界面如图5-2:
图5-2:使用Catagories属性的界面
Explicit属性
本属性忽略一个test和test fixture,直到它们显式的选择执行。如果test和test fixture在执行的过程中被发现,就忽略他们。所以,这样一来进度条显示为黄色,因为有test或test fixture忽略了。
例如:
Expected Exception属性
期望在运行时抛出一个期望的异常,如果是,则测试通过,否则不通过。
参看下面的例子:
5 . 测试生命周期合约
如果记得test case的定义,其中一个属性是测试的独立性或隔离性.
SetUp/TearDown方法提供达到测试隔离性的目的.
SetUp确保共享的资源在每个测试运行前正确初始化,
TearDown确保没有运行测试产生的遗留副作用.
TestFixtureSetUp/
TestFixtureTearDown同样提供相同的目的,但是却在test fixture范围里,我们刚才描述的内容组成了测试框架的运行时容器(test runner)和你写的测试之间的生命周期合约(
life-cycle contract).
为了描述这个合约,我们写一个简单的测试来说明什么方法调用了,怎么合适调用的.这里是代码:
1
using
System;
2
using
NUnit.Framework;
3
[TestFixture]
4
public
class
LifeCycleContractFixture
5

{
6
[TestFixtureSetUp]
7
public void FixtureSetUp()
8
{
9
Console.Out.WriteLine("FixtureSetUp");
10
}
11
12
[TestFixtureTearDown]
13
public void FixtureTearDown()
14
{
15
Console.Out.WriteLine("FixtureTearDown");
16
}
17
18
[SetUp]
19
public void SetUp()
20
{
21
Console.Out.WriteLine("SetUp");
22
}
23
24
[TearDown]
25
public void TearDown()
26
{
27
Console.Out.WriteLine("TearDown");
28
}
29
30
[Test]
31
public void Test1()
32
{
33
Console.Out.WriteLine("Test 1");
34
}
35
36
[Test]
37
public void Test2()
38
{
39
Console.Out.WriteLine("Test 2");
40
}
41
42
}
43

44
当编译和运行这个测试,可以在
System.Console窗口看到下面的输出:
FixtureSetUp
SetUp
Test
1
TearDown
SetUp
Test
2
TearDown
FixtureTearDown
可以看到,
SetUp/
TearDown方法调用在每个测试方法的前后.
整个fixture调用一次
TestFixtureSetUp/
TestFixtureTearDown方法.
下载:
1)NUnit的应用文档
下载
2)本问的PDF版
下载
参考
2) James W. Newkirk and Alexei A. Vorontsov ,Test-Driven Development in Microsoft .NET,Microsoft Press,2003
4) Kent Beck,
Test-Driven Development: By Example ,Addison-Wesley Professional, 2003
5) Andrew Hunt ,David Thomas ,Pragmatic Unit Testing In C# With NUnit
在.NET中如何测试Private和Protected方法?
How to Test Private and Protected methods in .NET, TimStall,
介绍
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方法. |
|
创建内部方法来访问private方法.然后在public方法包装那些private方法的程序集的其他地方,创建一个公共的测试类. |
- 通过增加内部钩子,以及最后使得private方法在产品中可用,会改编发布代码.
- 这需要很多额外得编码,因此使脆弱得.
|
测试Protected方法
Protected方法仅仅对于它得继承类可见,因此,对于测试套件来说并不是立即可见的.例如,激射我们想测试来自from ClassLibrary1.MyObject
的方法.
1
protected
string
MyProtectedMethod(
string
strInput,
int
i32Value)
2

{
3
return this.Name + ": " + strInput + ", " +
4
i32Value.ToString();
5
}
6
Pragmatic Unit Testing in C# with NUnit
一书解释了一个解决方案
:
创建一个继承自
MyObject
类的类
MyObjectTester,
然后创建一个
public
方法
TestMyProtectedMethod
,
这个方法包装了那个
protected
方法
.
例如
,
1
public
new
string
TestMyProtectedMethod(
string
strInput,
int
i32Value)
2

{
3
return base.MyProtectedMethod(strInput,
4
i32Value);
5
}
6
方法很简单
,
也遵循所有原则
:
原则 | 实现 |
透明 | 通过使用继承,并把MyObjectTester 类放入 UnitTests 程序集中,它不需要增加任何新的代码到产品程序集中. |
范围 | 在本方法中没有任何东西依赖Debug-only技术. |
简单 | 尽管这个方法需要一新的类,以及每个protected 方法的额外public包装方法,但是它是面向对象的,并且使类型安全的. |
测试Private方法
测试private方法需要多做有些工作,但是我们仍可以使用System.Reflection来实现.你可以使用反射来动态访问一种类型的方法, 包括实例和静态private方法的方法.注意访问private方法需要ReflectionPermission,但是对于运行在开发机器或者构建服务器上的单元测试来说,这不是问题.
假设我们想测试来自ClassLibrary1.MyObject
的private方法MyPrivateMethod
:
1
private
string
MyPrivateMethod(
string
strInput, DateTime dt,
double
2
dbl)
3

{
4
return this.Name + ": " + strInput + ", " +
5
dt.ToString() + ", " + dbl.ToString();
6
}
7
一个解决方法是创建一个UnitTestUtilities
工程,
这个工程有一个helper
类通过反射来调用测试方法.
例如,
供下载的解决方案在
UnitTestUtilities.Helper
中有如下方法:
1
public
static
object
RunStaticMethod(System.Type t,
string
strMethod,
2
object
[] aobjParams)
3

{
4
BindingFlags eFlags =
5
BindingFlags.Static | BindingFlags.Public |
6
BindingFlags.NonPublic;
7
return RunMethod(t, strMethod,
8
null, aobjParams, eFlags);
9
}
//
end of method
10
public
static
object
RunInstanceMethod(System.Type t,
string
strMethod,
11
object
objInstance,
object
[] aobjParams)
12

{
13
BindingFlags eFlags = BindingFlags.Instance | BindingFlags.Public |
14
BindingFlags.NonPublic;
15
return RunMethod(t, strMethod,
16
objInstance, aobjParams, eFlags);
17
}
//
end of method
18
private
static
object
RunMethod(System.Type t,
string
19
strMethod,
object
objInstance,
object
[] aobjParams, BindingFlags eFlags)
20

{
21
MethodInfo m;
22
try
23
{
24
m = t.GetMethod(strMethod, eFlags);
25
if (m == null)
26
{
27
throw new ArgumentException("There is no method '" +
28
strMethod + "' for type '" + t.ToString() + "'.");
29
}
30
31
object objRet = m.Invoke(objInstance, aobjParams);
32
return objRet;
33
}
34
catch
35
{
36
throw;
37
}
38
}
//
end of method
39
Private
方法
RunMethod
带有一些必要的参数,
这些参数是反射需要用来调用一个方法,
然后返回值的.
它有两个public
方法
RunStaticMethod
和
RunInstanceMethod
来为静态和实例方法分别包装这.
看看RunMethod
,它首先得到类型的MethodInfo.
因为我们期望它仅为已经存在的方法调用
.
一个空的方法触发一个
Exception
.
一旦我们有
MethodInfo
,我们就可以调用实例化对象提供的方法(static 方法为null)以及参数数组.
我们可以在一个NUnit测试中像下面使用这个Utility:
1
[Test]
public
void
TestPrivateInstanceMethod()
2

{
3
string strExpected = "MyName: Hello, 5/24/2004
4
12:00:00 AM, 2.1";
5
6
ClassLibrary1.MyObject objInstance
7
= new MyObject("MyName");
8
9
object obj =
10
UnitTestUtilities.Helper.RunInstanceMethod(
11
typeof(ClassLibrary1.MyObject), "MyPrivateMethod",
12
objInstance, new object[3]
{"Hello",
13
new DateTime(2004,05,24), 2.1});
14
15
string strActual = Convert.ToString(obj);
16
17
Assert.AreEqual(strExpected,strActual);
18
}
19
原则 | 实现 |
透明 | 我们仅创建的多余代码; 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类.这些技术都能帮助你改进测试覆盖面.
原文:How to Test Private and Protected methods in .NET, TimStall,
原文转自:
http://confach.cnblogs.com/archive/2005/06/20/177817.html
http://confach.cnblogs.com/articles/225502.html
作者: