让我们来为我们的Account的代码添加一些错误检测。为账户添加一个最小余额限制,通过你的最小透支保护费来维持它的持续运作。首先我们来为Account类添加一个最小余额保护属性:
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可以监视测试程序集的变化,并在测试树结构发生变化时进行更新——例如,添加了新的测试)。点击“Run”按钮——我们又一次得到了一个红色的状态条。我们得到了下面的失败消息:“TransferWithInsufficentFunds: InsufficientFundsException was expected”。我们来再次修改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()方法抛出了另外一个异常呢?我们应该在catch块中执行一个补救处理,还是依赖我们的交易管理器来重新装载对象的状态?某些时候我们必须回答这样的问题,但不是现在;可我们眼前如何应付这个失败的测试呢——删除它?一个不错的方法是临时忽略它在你的测试方法中添加下面的特性:
[Test]
[Ignore("Need to decide how to implement transaction management in the application")]
public void TransferWithInsufficientFundsAtomicity()
{
// code is the same
}
编译并运行——黄条。单击“Test Not Run”选项卡,你会看到bank.AccountTest.TransferWithInsufficientFundsAtomicity()连同这个测试被忽略的原因一起列在列表中。
看看我们的测试代码,我们可以看到一些适宜的重构。所有的方法共享一组公共的测试对象。让我们来将这些初始化代码放到一个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("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);
}
}
}
注意这个初始化方法拥有通用的初始化代码,它的返回值类型为void,没有参数,并且由[SetUp]特性标记。编译并运行——同样的黄条!
============全文完==========
如果你安装了NUnit V2.1,可以在 开始->程序->NUnit V2.1->QuickStart 处得到原文(英文版)。