NUnit 文档翻译开始
节选部分
NUnit Quick Start
Note: 本页面是基于早期发行的NUnit版本中的 QuickStart.doc 文件。它被认为不是一个好的测试驱动开发的例子。但是我们还是在文档中保留了它。因为它 讲解了使用NUnit的基础知识。我们会在将来的版本中重写或替换它
让我们从简单的例子开始. 假设我们现在正在写一个空的应用程序,其中包含一个 Account 类. Account 提供了 deposit, withdraw, 和 transfer funds操作. Account 类可能类似下面的代码:
namespace bank { 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;} } } }
现在让我们来针对这些写一个测试类 AccountTest 类. 我们要测的第一个方法是 TransferFunds.
namespace bank { using NUnit.Framework; [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); } } }
我们首先注意到是,在类的定义前有一个 [TestFixture] 属性 . 这个属性表明类中包含了测试代码 (这个属性可以继承). 这个测试的类必须是 public 的,并且没有超类的制约. 最后他还必须有一个默认的构造函数.
类中唯一的方法 TransferFunds, 有一个 [Test] 属性的头文字,这表示他是一个测试方法. 测试方法必须是返回空值切不能有参数表. 在测试示例中,对测试对象进行了通常的初始化处理,执行测试的业务,最后检验业务对象的状态. Assert 类定义了用于检验 post-conditions 的方法集合。在上面的示例中我们使用了 AreEqual 方法来确认执行过TransferFunds后,两个账户余额是否正确(AreEqual有一些重载函数,在此我们使用的如下的参数 : 第一个参数是期望值,第二个参数是实际值).
编译运行这个例程. 假设你把项目编译成一个 bank.dll 文件. 运行 NUnit Gui (安装向导会在桌面和 Program Files 目录创快捷方式), 启动 GUI 后, 点击 File->Open 菜单项, 打开你的 bank.dll 文件. bank.dll 被装载后,在左边的panel中我们会看到一个树型的测试结构,而在右边的panel中会出现一个状态集合. 点击 Run 按钮, 进度状态条和树型结构中的 TransferFunds 节点变成红色,说明测试失败. 在[Errors]和[Failures] panel 中显示如下的消息:
TransferFunds : expected <250> but was <150>并且在这个消息的下面有一个堆栈跟踪的 panel ,他指出了错误测试代码的位置
at bank.AccountTest.TransferFunds() in C:\nunit\BankSampleTests\AccountTest.cs:line 17
这是意料之中的结果; 因为 TransferFunds 方法尚未实现,所以导致了测试失败. 不必关闭 GUI ,回到 IDE 按照下面的代码修正 TransferFunds 方法:
public void TransferFunds(Account destination, float amount) { destination.Deposit(amount); Withdraw(amount); }
现在重新编译代码,然后再次运行 GUI ,状态条与测试树都变成了绿色. (只要你保持 GUI 始终处在打开状态, 你可以随意的在 IDE 中修改并编译代码,而 GUI 会自动重新载入dll.
我们可以向 Account 中加入更多的错误测试代码. 我们将添加一个最小账户余额的需求,用于确定是否需要向银行交纳小于最小账户余额时的费用. 首先我们向 Account 类增加一个 minimumBalance 属性:
private float minimumBalance = 10.00F; public float MinimumBalance { get{ return minimumBalance;} }
使用一个异常来表示低于最小余额:
namespace bank { using System; public 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] 属性指出了下面的测试中我们期望得到一个确定类型的异常; 如果这个异常没有发生了那末测试失败. 编译代码后回到 GUI. 当你重新编译代码时, GUI 会使测试树灰掉,并且收起所有分支,就象测试没有运行之前一样 (GUI 会监视DLL的所有变化并且及时地反映出来). 点击 Run 按钮红色的状态条又出现了. 我们得到了如下的错误 :
TransferWithInsufficentFunds : InsufficientFundsException was expected下面我们来修改一下 TransferFunds 方法:
public void TransferFunds(Account destination, float amount) { destination.Deposit(amount); if(balance-amount<minimumBalance) throw new InsufficientFundsException(); Withdraw(amount); }
编译后运行测试绿色的状态条出现了. 成功! 但是等一等, 仔细看一下我们的代码会发现每当 Transfer 操作失败时银行都会丢失一笔钱. 我们来写一个测试来确认这种怀疑. 添加这个测试方法:
[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.00 蒸发掉了 (1999.com 有过此经历吗?) 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() 方法抛出另外的异常会发生什么情况呢? Should we execute a compensating transaction in the catch block or rely on our transaction manager to restore the state of the objects? We need to answer those questions at some point, but not now; but what do we do with the failing test in the meantime remove it? 一个更好的办法是暂时忽略它,在方法添加下面的属性
[Test] [Ignore("Decide how to implement transaction management")] public void TransferWithInsufficientFundsAtomicity() { // code is the same }
编译,执行后出现了黄色的状态条. 点击[Tests Not Run]标签你会看到 bank.AccountTest.TransferWithInsufficientFundsAtomicity() in the list along with the Reason this test is ignored.
审视我们的代码,我们可以发现有些地方需要重构. 所有的测试方法共享了一组对象. 让我们提取这些代码放到一个 setup 方法中,这样就可在所有的方法中重用了. 重构后的测试类代码如下:
namespace bank { using System; using NUnit.Framework; [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("Decide how to implement transaction management")] public void TransferWithInsufficientFundsAtomicity() { try { source.TransferFunds(destination, 300.00F); } catch(InsufficientFundsException expected) { } Assert.AreEqual(200.00F,source.Balance); Assert.AreEqual(150.00F,destination.Balance); } } }
请注意 Init 方法拥有共同的初始化代码, 它没有返回值, 没有参数, 他用 [SetUp] 属性来标识. Compile and run same yellow bar!