如何写Unit Test, TDD 入门1

我将讲述这个框架如何使用.同时也涉及到一些非常重要的概念,我想其客户属性是非常重要的.在NUnit里,有以下几种属性:
  • TestFixture
  • Test
下面我将对每种属性一一讲解.

TestFixtureAttribute

本属性标记一个类包含测试,当然setup和teardown方法可有可无.(关于setup 和teardown方法在后面介绍)
做为一个测试的类,这个类还有一些限制
  • 必须是Public,否则NUnit看不到它的存在.
  • 它必须有一个缺省的构造函数,否则是NUnit不会构造它.
  • 构造函数应该没有任何副作用,因为NUnit在运行时经常会构造这个类多次,如果要是构造函数要什么副作用的话,那不是乱了.
举个例子
 
 

1 using System;
2 using NUnit.Framework;
3 namespace MyTest.Tests
4

{
5
6[TestFixture]
7publicclassPriceFixture
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]
7publicclassSuccessTests
8

{
9[Test]publicvoidTest1()
10

{/**/}
11}

12}

13
14


一般来说,有了上面两个属性,你可以做基本的事情了.

另外,我们再对如何进行比较做一个描述。
在NUnit中,用Assert(断言)进行比较,Assert是一个类,它包括以下方法:AreEqual,AreSame,Equals, Fail,Ignore,IsFalse,IsNotNull,具体请参看NUnit的文档。

如何在.NET中应用NUnit

我将举个例子,一步一步演示如何去使用NUnit.

为测试代码创建一个Visual Studio工程。

在Microsoft Visual Studio .NET中,让我们开始创建一个新的工程。选择Visual C#工程作为工程类型,Class Library作为模板。将工程命名为 NUnitQuickStart.图4-1是一个描述本步骤的Visual Studio .NET。

图 4-1: 创建第一个NUnit工程

增加一个NUnit框架引用

在Microsoft Visual Studio .NET里创建这个例子时,你需要增加一个 NUnit.framework.dll引用,如下:
在Solution Explorer右击引用,然后选择增加引用
NUnit.framework组件,在Add Reference对话框中按Select和OK按钮。
图4-2 描述了这步:

图 4-2: 增加一个 NUnit.framework.dll 引用到工程

为工程加一个类.

为工程加一个 NumbersFixture类。这里是这个例子的代码。
1 using System;
2 using NUnit.Framework;
3
4 namespace NUnitQuickStart
5

{
6[TestFixture]
7publicclassNumersFixture
8

{
9[Test]
10publicvoidAddTwoNumbers()
11

{
12inta=1;
13intb=2;
14intsum=a+b;
15Assert.AreEqual(sum,3);
16}

17}

18}

19

建立你的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 帮助描述本步骤:
图 4-3: 将NUnit-Gui 作为工程的测试运行器

编译运行测试.

现在编译solution。成功编译后,开始应用程序。NUnit-Gui测试运行器出现。当你第一次开始NUnit-Gui,它打开时没有测试加载。从File菜单选择Oprn,浏览 NUnitQuickStart. dll的路径。当你加载了测试的程序集,测试运行器为加载的程序集的测试产生一个可见的表现。在例子中,测试程序集仅有一个测试,测试程序集的结构如图4-4所示:

图 4-4: 测试程序集的测试在 NUnit-Gui中的视图
按Run按钮。树的节点变为绿色,而且测试运行器窗口上的进度条变绿,绿色代表成功通过。

其他的一些核心概念

上面的例子介绍了基本的NUnit特性和功能. TestFixture,Test,和 Assert是3个最基本的特征,我们可以用这些特性进行程序员测试了.但是有的时候,你觉得这3个远远不够,比如有的时候打开一个数据库连接多次,有没有只让它打开一次的方法呢?如果我想把测试分类,应该怎样实现呢?如果我想忽略某些测试,又应该如何去完成呢?不用担心,NUnit已经有这样的功能了.
下面我们一一作出回答.

SetUp/TearDown 属性

在早期给的test fixture定义里,我们说test fixture的测试是一组常规运行时资源.在测试完成之后,或是在测试执行种,或是释放或清除之前,这些常规运行时资源在一确定的方式上可能需要获取和初始化.NUnit使用2个额外的属性: SetUpTearDown,就支持这种常规的初始化/清除.我们上面的例子来描述这个功能.让我们增加乘法.
1 using System;
2 using NUnit.Framework;
3
4 namespace NUnitQuickStart
5

{
6[TestFixture]
7publicclassNumersFixture
8

{
9[Test]
10publicvoidAddTwoNumbers()
11

{
12inta=1;
13intb=2;
14intsum=a+b;
15Assert.AreEqual(sum,3);
16}

17[Test]
18publicvoidMultiplyTwoNumbers()
19

{
20inta=1;
21intb=2;
22intproduct=a*b;
23Assert.AreEqual(2,product);
24}

25
26}

27}

28
我们仔细一看,不对,有重复的代码,如何去除重复的代码呢?我们可以提取这些代码到一个独立的方法,然后标志这个方法为 SetUp 属性,这样2个测试方法可以共享对操作数的初始化了,这里是改动后的代码:
1 using System;
2 using NUnit.Framework;
3
4 namespace NUnitQuickStart
5

{
6[TestFixture]
7publicclassNumersFixture
8

{
9privateinta;
10privateintb;
11[SetUp]
12publicvoidInitializeOperands()
13

{
14a=1;
15b=2;
16}

17
18[Test]
19publicvoidAddTwoNumbers()
20

{
21intsum=a+b;
22Assert.AreEqual(sum,3);
23}

24[Test]
25publicvoidMultiplyTwoNumbers()
26

{
27intproduct=a*b;
28Assert.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

{
5intzero=0;
6intinfinity=a/zero;
7Assert.Fail("Shouldhavegottenanexception");
8}

9


除了[Test]属性之外, DivideByZero方法有另外一个客户属性: ExpectedException.在这个属性里,你可以在执行过程中捕获你期望的异常类型,例如在本例就是DivideByZeroException.如果这个方法在没有抛出期望异常的情况下完成了,这个测试失败.使用这个属性帮助我们写程序员测试验证边界条件(Boundary Conditions).

Ignore 属性

由于种种原因,有一些测试我们不想运行.当然,这些原因可能包括你认为这个测试还没有完成,这个测试正在重构之中,这个测试的需求不是太明确.但你有不想破坏测试,不然进度条可是红色的哟.怎么办?使用 Ignore属性.你可以保持测试,但又不运行它们.让我们标记 MultiplyTwoNumbers测试方法为 Ignore属性:
 
 

1 [Test]
2 [Ignore( " Multiplicationisignored " )]
3 public void MultiplyTwoNumbers()
4

{
5intproduct=a*b;
6Assert.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]
7publicvoidOpenConnection()
8

{
9//opentheconnectiontothedatabase
10}

11
12[TestFixtureTearDown]
13publicvoidCloseConnection()
14

{
15//closetheconnectiontothedatabase
16}

17
18[SetUp]
19publicvoidCreateDatabaseObjects()
20

{
21//inserttherecordsintothedatabasetable
22}

23
24[TearDown]
25publicvoidDeleteDatabaseObjects()
26

{
27//removetheinsertedrecordsfromthedatabasetable
28}

29
30[Test]
31publicvoidReadOneObject()
32

{
33//loadonerecordusingtheopendatabaseconnection
34}

35
36[Test]
37publicvoidReadManyObjects()
38

{
39//loadmanyrecordsusingtheopendatabaseconnection
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

{
3usingSystem;
4usingNUnit.Framework;
5
6
7
8publicclassAllTests
9

{
10[Suite]
11publicstaticTestSuiteSuite
12

{
13get
14

{
15TestSuitesuite=newTestSuite("AllTests");
16suite.Add(newOneTestCase());
17suite.Add(newAssemblies.AssemblyTests());
18suite.Add(newAssertionTest());
19returnsuite;
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]
7publicclassNumersFixture
8

{
9privateinta;
10privateintb;
11[SetUp]
12publicvoidInitializeOperands()
13

{
14a=1;
15b=2;
16}

17
18[Test]
19[Category("Numbers")]
20publicvoidAddTwoNumbers()
21

{
22intsum=a+b;
23Assert.AreEqual(sum,3);
24}

25
26[Test]
27[Category("Exception")]
28[ExpectedException(typeof(DivideByZeroException))]
29publicvoidDivideByZero()
30

{
31intzero=0;
32intinfinity=a/zero;
33Assert.Fail("Shouldhavegottenanexception");
34}

35[Test]
36[Ignore("Multiplicationisignored")]
37[Category("Numbers")]
38publicvoidMultiplyTwoNumbers()
39

{
40intproduct=a*b;
41Assert.AreEqual(2,product);
42}

43
44}

45


NUnit-GUI界面如图5-2:
 
图5-2:使用Catagories属性的界面

Explicit属性

本属性忽略一个test和test fixture,直到它们显式的选择执行。如果test和test fixture在执行的过程中被发现,就忽略他们。所以,这样一来进度条显示为黄色,因为有test或test fixture忽略了。
例如:
1
2 [Test,Explicit]
3 [Category( " Exception " )]
4 [ExpectedException( typeof (DivideByZeroException))]
5 public void DivideByZero()
6

{
7intzero=0;
8intinfinity=a/zero;
9Assert.Fail("Shouldhavegottenanexception");
10}

11
为什么会设计成这样呢?原因是Ingore属性忽略了某个test或test fixture,那么他们你再想调用执行是不可能的。那么万一有一天我想调用被忽略的test或test fixture怎么办,就用Explicit属性了。我想这就是其中的原因吧。

Expected Exception属性

期望在运行时抛出一个期望的异常,如果是,则测试通过,否则不通过。
参看下面的例子:
1 [Test]
2 [ExpectedException(typeofInvalidOperationException))]
3 public void ExpectAnException()
4

{
5intzero=0;
6intinfinity=a/zero;
7Assert.Fail("Shouldhavegottenanexception");
8
9}

10
在本测试中,应该抛出DivideByZeroException,但是期望的是InvalidOperationException,所以不能通过。如果我们将[ExpectedException(typeof(InvalidOperationException))]改为[ExpectedException(typeof(DivideByZeroException))],本测试通过。

测试生命周期合约

如果记得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]
7publicvoidFixtureSetUp()
8

{
9Console.Out.WriteLine("FixtureSetUp");
10}

11
12[TestFixtureTearDown]
13publicvoidFixtureTearDown()
14

{
15Console.Out.WriteLine("FixtureTearDown");
16}

17
18[SetUp]
19publicvoidSetUp()
20

{
21Console.Out.WriteLine("SetUp");
22}

23
24[TearDown]
25publicvoidTearDown()
26

{
27Console.Out.WriteLine("TearDown");
28}

29
30[Test]
31publicvoidTest1()
32

{
33Console.Out.WriteLine("Test1");
34}

35
36[Test]
37publicvoidTest2()
38

{
39Console.Out.WriteLine("Test2");
40}

41
42}

43
44


当编译和运行这个测试,可以在 System.Console窗口看到下面的输出:
 
 

FixtureSetUp
SetUp
Test
1

TearDown
SetUp
Test
2
TearDown
FixtureTearDown


可以看到, SetUp/ TearDown方法调用在每个测试方法的前后. 整个fixture调用一次 TestFixtureSetUp/ TestFixtureTearDown方法.
下载:
NUnit的应用文档 下载

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结