1. 介绍
TestDriven.NET(目前只支持到Nunit 2.4版本,对2.5的一些新功能还不支持):
TestDriven.Net现在是一种商业化的工具,只有个人版可以免费下载使用,个人认为在开发中个人版的功能已经足够了,它的功能十分强大,它的前身是NUnitAddIn,由Jamie Cansdale开发,现在TestDriven.NET 2.0版本也已经发布,最新的2.0版本集成了.NET Reflector、NConver、NConverExplorer、TypeMock.NET等,功能更加强大。它使用很简单,大家可以参考官方文档
Resharper :
是一个功能丰富的IDE插件,支持从代码整理到代码分析等各种功能,对Nunit的支持是它的功能之一,个人认为是一个相当不错的工具,开发过程可以将好 多工作自动完成,如果是一个菜鸟的话作用就更大了,可以帮助改善个人的编码习惯,使你的代码看起来要好很多,先最新版本是4,感兴趣的可以去找一下,网上 的破解版本也挺多的(虽然鄙视盗版,但以经济建设为中心也没办法啊)。
选择Nunit的理由:
1.开源工具(免费的),专注于测试框架的开发,功能丰富上手简单,使用方便。
2.提供了比.NET框架更加丰富和易用的断言模型。
3.运行方便、配置灵活,可以方便的对包含测试方法的dll进行测试,通过树形菜单支持对多个程序集进行测试,并支持对测试方法进行分组管理。
4.对测试结果可以选择性的保存,省去了手动清理垃圾文件的时间。
5.测试方法与实际代码是紧密结合的,便于在更改代码的时候同时维护测试用例,而且方便在脱离代码和开发环境下进行测试,比如新部署之后可以直接执行一般测试查找问题而不必用代码去跟可能出现的问题。而VS.NET的测试方案无法脱离VS.NET开发环境独立存在。
2.使用说明(官方文档 )
从www.nunit.org 下载最新的安装包安装即可。如果要在工程总添加基于Nunit的单元测试,执行如下步骤:首先在工程引用中添加Nunit.Framework的引用,该 引用位于.NET选项卡,假如我们要测试的类名为Account.cs,则约定我们的测试类名为AccountTest.cs,然后再该测试类中添加测试 代码。Nunit通过特性(attribute)来标记一个测试类或者测试方法,其基本形式如下:
[TestFixture] public class AccountTest //标识一个测试类 //标识一个函数是测试方法入口 [Test] public void TransferFunds() [SetUp]//执行测试前的一些初始化操作 public void Init() { source = new Account(); source.Deposit(200.00F); destination = new Account(); destination.Deposit(150.00F); } [TearDown] public void Dispose() //执行测试完成后的资源清理操作 {
} |
下面我们通过一个网上比较流行的例子来了解一下Nunit的具体使用方法(感谢整理这个例子的大侠):
假设我们正在编写一个银行应用程序,而我们这样一个基本类——Account。Account支持存款、取款和资金转帐。这个Account类看起来会是这个样子:
namespace UnitTest { //账户类 public class Account { private float balance; //存款 public void Deposit(float amount) { balance += amount; } //取款 public void Withdraw(float amount) { balance -= amount; } //转账 public void TransferFunds(Account destination, float amount) { } //余额 public float Balance { get { return balance; } } }//End Class }//End Namespace |
现在我们来为这个类写一个测试——AccountTest。我们要测试的第一个类方法是TransferFunds。
using NUnit.Framework; using System; namespace UnitTest { [TestFixture] public class AccountTest { [Test] public void TransferFunds() { Account source = new Account(); //初始化转出帐号 source.Deposit(200.00F); Account destination = new Account();//转入帐号 destination.Deposit(150.00F); source.TransferFunds(destination, 100.00F); Assert.AreEqual(250.00F, destination.Balance); Assert.AreEqual(100.00F, source.Balance); } }//End Class }//End Namespace |
首先这个类关联了一个[TestFixture]特性(attribute)——这表示这个类包含了测试代码(这个特性可以被继承)。这个类必须是公有的,但他的父类并不受限制。这个类还必须有一个默认构造函数。
类中唯一的一个方法——TransferFunds(),关联了一个[Test]特性——这表示它是一个测试方法。测试方法的返回值必须为void并 且不能带有参数。在我们的测试方法中,我们对被测试的对象进行了一般的初始化,执行了被测试的方法并检查了对象的状态。Assert类定义了一组方法用于 检查执行结果是否符合预期设计,在例子中我们使用了AreEqual()方法来确保交易过后两个账户都有正确的余额(这个方法有很多重载(更多的介绍见后 面的附录),我们在这个例子中使用的版本带有两个参数:第一个参数是我们的期望值,第二个参数是实际值)。
编译并运行这个例子。假设你已经将你的测试代码编译为bank.dll。打开NUint Gui(桌面或“程序”菜单中的一个快捷方式),打开GUI后,选择File->Open菜单项,找到你的bank.dll并在 “Open”对话框中选中它。bank.dll装载后你会在左边的面板中看到一个测试树结构,还有右边的一组状态面板。单击Run按钮可以执行选定的测 试。在这里我使用的是Reshaper提供的一个集成到IDE内的插件来执行的,执行状态如下图所示,状态条和测试树种的TransferFunds节点 变成了红色——我们的测试失败了。
这正是预期的结果,因为我们还未实现TransferFunds()方法。 修改代码,使你的TransferFunds()方法看起来像这样:
//转账 public void TransferFunds(Account destination, float amount) { destination.Deposit(amount); Withdraw(amount); } |
再次测试——状态条和数节点变绿了。
让我们来为Account的代码添加一些错误检测。为账户添加一个最小余额限制,通过最小透支保护费来维持它的运作。首先我们来为Account类添加一个最小余额保护属性:
public float
minimumBalance = 10.00F; /// <summary> /// 最小余额限制 /// </summary> public float MinimumBalance { get { return minimumBalance; } } |
我们使用一个异常来指出透支:
namespace UnitTest { class InsufficientFundsException: ApplicationException { } } |
向我们的AccountTest类添加一个新的方法
[Test] [ExpectedException(typeof(InsufficientFundsException))] public void TransferWithInsufficientFunds() { Account source = new Account(); source.Deposit(200.00F); Account destination = new Account(); destination.Deposit(150.00F); source.TransferFunds(destination, 300.00F); } |
这个测试方法除了[Test]特性之外还关联了一个[ExpectedException]特性——这指出测试代码希望抛出一个指定类型的异常;如果在执 行过程中没有抛出这样的一个异常——该测试将会失败。在测试类中点击右键选择Run Unit Tests,结果如下图所示:
我们来再次修改Account的代码,象下面这样修改TransferFunds()方法:
public void TransferFunds(Account destination, float amount) { destination.Deposit(amount); if (balance - amount < minimumBalance) throw new InsufficientFundsException(); Withdraw(amount); } |
编译并运行测试——绿了。成功!不过等等,看看我们刚写的代码,我们会发现银行在每一笔不成功的转账操作时都亏钱了。让我们来写一个测试来确认我们的猜测。添加这个测试方法:
[Test] public void TransferWithInsufficientFundsAtomicity() { Account source = new Account(); source.Deposit(200.00F); Account destination = new Account(); destination.Deposit(150.00F); try { source.TransferFunds(destination, 300.00F); } catch (InsufficientFundsException expected) { } Assert.AreEqual(200.00F, source.Balance); Assert.AreEqual(150.00F, destination.Balance); } |
我们测试了方法的交易属性——是否所有的操作都成功了。编译并运行——红条。我们发现平白无故地损失了300块钱——source账户有正确的余额 150.00,但destination账户显示:$450.00。我们该如何修改?我们能够只将最小余额检查的调用放到数据更新的前面么:
public void TransferFunds(Account destination, float amount) { if (balance - amount < minimumBalance) { throw new InsufficientFundsException(); } destination.Deposit(amount); Withdraw(amount); } |
这时如果Withdraw()方法抛出了另外一个异常呢?一个不错的方法是临时忽略它在你的测试方法中添加下面的特性:
[Test] [Ignore("Need to decide how to implement transaction management in the application")] public void TransferWithInsufficientFundsAtomicity() { // code is the same } |
测试结果——黄。
我们可以看到一些适宜的重构。所有的方法共享一组公共的测试对象。让我们来将这些初始化代码放到一个setup方法中并在所有的测试中重用它们。我们的测试类的重构版本像下面这样:
using NUnit.Framework; using System; namespace UnitTest { [TestFixture] public class AccountTest { Account source; Account destination; [SetUp] public void Init() { source = new Account(); source.Deposit(200.00F); destination = new Account(); destination.Deposit(150.00F); } [Test] public void TransferFunds() { source.TransferFunds(destination, 100.00f); Assert.AreEqual(250.00F, destination.Balance); Assert.AreEqual(100.00F, source.Balance); } [Test] [ExpectedException(typeof(InsufficientFundsException))] public void TransferWithInsufficientFunds() { source.TransferFunds(destination, 300.00F); } [Test, Ignore( "Need to decide how to implement transaction management in the application" )] public void TransferWithInsufficientFundsAtomicity() { try { source.TransferFunds(destination, 300.00F); } catch (InsufficientFundsException expected) { } Assert.AreEqual(200.00F, source.Balance); Assert.AreEqual(150.00F, destination.Balance); } }//End Class }//End Namespace |
注意这个初始化方法拥有通用的初始化代码,它的返回值类型为void,没有参数,并且由[SetUp]特性标记。编译并运行——同样的黄条!