NUnit Quick Start

原文:http://www.nunit.org/index.php?p=quickStart&r=2.5.3

 

从简单的实例开始,假设我们正在开发一个银行应用系统,已有一个基本的领域类“Account”,“Account”类支持存款、取款和转账操作。代码如下:

 1 namespace bank
 2 {
 3   public class Account
 4   {
 5     private float balance;
 6     public void Deposit(float amount)
 7     {
 8       balance+=amount;
 9     }
10 
11     public void Withdraw(float amount)
12     {
13       balance-=amount;
14     }
15 
16     public void TransferFunds(Account destination, float amount)
17     {
18     }
19 
20     public float Balance
21     {
22       getreturn balance;}
23     }
24   }
25 }

 

现在给这个类编写一个测试类“AccountTest”。 第一个测试方法为“ TransferFunds”。

 1 namespace bank
 2 {
 3   using NUnit.Framework;
 4 
 5   [TestFixture]
 6   public class AccountTest
 7   {
 8     [Test]
 9     public void TransferFunds()
10     {
11       Account source = new Account();
12       source.Deposit(200.00F);
13       Account destination = new Account();
14       destination.Deposit(150.00F);
15 
16       source.TransferFunds(destination, 100.00F);
17       Assert.AreEqual(250.00F, destination.Balance);
18       Assert.AreEqual(100.00F, source.Balance);
19     
20     }
21   }
22 }

首先,我们注意到这个类有一个 [TestFixture]特性和它关联,这是表明该包含了测试代码(该特性可以继承)。这个类必须是“public"的,但对于它的超类是没有此约束的。且该测试类必须要有一个默认的构造函数。

 

测试类中唯一的测试方法” TransferFunds“,有一个[Test]特性与它关联,这表该方法是测试方法。测试方法没有返回值和参数。在这个测试方法中,我们对被测试对象做了些通常的初始化工作,执行被测试的业务方法,并检查业务对象的状态。Assert类定义了一系列方法用于检验”后置条件“。在该测试方法中我们使用了”AreEqual“方法来确保在两个账户中进行转账后账户结存是平衡的(AreEqual方法有几个重载,在我们示例中使用的是以下参数:第一参数是期望值,第二参数是实际值)。

 

编译并运行示例。假定我们已经把测试代码编译成了bank.dll。启动NUint GUi界面(安装程序会在桌面上创建一个快捷方式),启动界面后,选择File->Open菜单项,在打开对话框中导航到bank.dll并选中它。加载bank.dll后,左边的面板上你可以看到一个树形结构,右边有一些状态面板。点击“Run”按钮,状态条和树形结构中的TransferFunds节点变红了——我们的测试失败。“Errors and Failures”面板显示如下信息:

 

 TransferFunds : expected <250> but was <150>

 

 在它下面的堆栈信息面板中报告了产生失败的测试代码位置

 

at bank.AccountTest.TransferFunds() in C:\nunit\BankSampleTests\AccountTest.cs:line 17

 

 

 这是预料之中的;测试失败的原因是我们还没有实现“TransferFunds”方法。现在让我们把它完善。不要关了NUnit界面,返回IDE并完善该方法,代码如下:

1 public void TransferFunds(Account destination, float amount)
2 {
3     destination.Deposit(amount);
4     Withdraw(amount);
5 }

 

 重新编译代码并再次点击”Run“按钮——状态条和测试树形结构变绿了。(注意NUnit GUI自动加载了程序集; 是因为我们一直打开NUnit GUI界面并继续在IDE里编写代码和写测试)。

 

让我们在Account类中添加一些错误检验。 在类添加最低余额需求,以确保银行继续向您收取最低透支保护费。给类添加属性,代码如下:

1 private float minimumBalance = 10.00F;
2 public float MinimumBalance
3 {
4     getreturn minimumBalance;}
5 }

 

 使用异常标识透支

1 namespace bank
2 {
3   using System;
4   public class InsufficientFundsException : ApplicationException
5   {
6   }
7 }

 

 添加新的测试方法:

 1 [Test]
 2 [ExpectedException(typeof(InsufficientFundsException))]
 3 public void TransferWithInsufficientFunds()
 4 {
 5     Account source = new Account();
 6     source.Deposit(200.00F);
 7     Account destination = new Account();
 8     destination.Deposit(150.00F);
 9     source.TransferFunds(destination, 300.00F);
10 }

 

 该测试方法除了[Test]特性外,还有[ExpectedException]特性与它相关联——标明该测试代码期望一个特定的异常抛出; 如果在执行时没有抛出异常——该测试失败。编译代码并返回NUint GUI界面。当在编辑代码时,GUI界面变灰并且折叠树形节点就像没有运行测试一样(GUI监视测试程序集所做的更改,当测试树形结构改变后自动更新——比如新的测试方法加入)。点击”Run“按钮——红色状态条再次出现,有如下失败信息:

 TransferWithInsufficentFunds : InsufficientFundsException was expected

 再次修改TransferFunds方法如下:

 1 public void TransferFunds(Account destination, float amount)

2 {
3     destination.Deposit(amount);
4     if(balance-amount<minimumBalance)
5         throw new InsufficientFundsException();
6     Withdraw(amount);
7 }

 

 编译并测试——绿色状态条。成功了!但是等等,查看刚才写的代码,你会发现当转账不成功时银行是会有损失。我们写个测试方法确认我们的猜疑。方法如下:

  1 [Test]

 2 public void TransferWithInsufficientFundsAtomicity()
 3 {
 4     Account source = new Account();
 5     source.Deposit(200.00F);
 6     Account destination = new Account();
 7     destination.Deposit(150.00F);
 8     try
 9     {
10         source.TransferFunds(destination, 300.00F);
11     }
12     catch(InsufficientFundsException expected)
13     {
14     }
15 
16     Assert.AreEqual(200.00F,source.Balance);
17     Assert.AreEqual(150.00F,destination.Balance);
18 }

 

 我们正在测试业务方法的事物属性——所有的操作要么成功要么失败。编译并运行——红色状态条。OK,300美元无中生有——源账户正确当时目标账户显示的是:450美元。如何修改?只需要把最小结余检查放在账户更新的前面就可以了:

1 public void TransferFunds(Account destination, float amount)
2 {
3     if(balance-amount<minimumBalance) 
4         throw new InsufficientFundsException();
5     destination.Deposit(amount);
6     Withdraw(amount);
7 }

 

 如果Withdraw()方法抛出另外一个异常怎么办?我们是否应该在异常捕获模块中补偿交易还是依赖我们的事物管理重新恢复对象状态?我们需要在某个时刻回答这些问题,而不是现在;如何解决在此期间失败的测试的代码——移除它?一个更好的办法就是暂时忽略它,给测试方法添加如下特性:

 1 [Test]

2 [Ignore("Decide how to implement transaction management")]
3 public void TransferWithInsufficientFundsAtomicity()
4 {
5     // code is the same
6 }

 

 编译并运行--黄色状态条。点击”Tests Not Run“标签,你会发现bank.AccountTest.TransferWithInsufficientFundsAtomicity()和该方法被忽视的原因显示在里面。

 

检查我们的测试代码, 有些是可以重构的。所有的测试方法共享一组测试对象。编写一个设置方法,提取出初始化代码放入其中,以便所有的测试方法重用。重构后的测试类代码如下:

  1 namespace bank

 2 {
 3   using System;
 4   using NUnit.Framework;
 5 
 6   [TestFixture]
 7   public class AccountTest
 8   {
 9     Account source;
10     Account destination;
11 
12     [SetUp]
13     public void Init()
14     {
15       source = new Account();
16       source.Deposit(200.00F);
17       destination = new Account();
18       destination.Deposit(150.00F);
19     }
20 
21     [Test]
22     public void TransferFunds()
23     {
24       source.TransferFunds(destination, 100.00f);
25       Assert.AreEqual(250.00F, destination.Balance);
26       Assert.AreEqual(100.00F, source.Balance);
27     }
28 
29     [Test]
30     [ExpectedException(typeof(InsufficientFundsException))]
31     public void TransferWithInsufficientFunds()
32     {
33       source.TransferFunds(destination, 300.00F);
34     }
35 
36     [Test]
37     [Ignore("Decide how to implement transaction management")]
38     public void TransferWithInsufficientFundsAtomicity()
39     {
40       try
41       {
42         source.TransferFunds(destination, 300.00F);
43       }
44       catch(InsufficientFundsException expected)
45       {
46       }
47 
48       Assert.AreEqual(200.00F,source.Balance);
49       Assert.AreEqual(150.00F,destination.Balance);
50     }
51   }
52 }

 

 注意:Init()方法初始化共用的代码,无返回值,无参数,并且标有[SetUp]特性。编译并运行——同样是黄色状态条。

 

posted @ 2010-03-24 11:43  Jim哥  阅读(223)  评论(0)    收藏  举报