标签:
(源码下载地址:http://pan.baidu.com/s/1c0EV9o0)
本演练将使用托管代码的 Microsoft 单元测试框架和 Visual Studio 测试资源管理器引导你逐步完成一系列单元测试的创建、运行和自定义。 你将从正处于开发过程中的 C# 项目开始,创建执行该项目代码的测试,运行测试并检查结果。 然后,可以更改项目代码并重新运行测试。
本主题包含以下各节:
说明 |
|
本演练对托管代码使用 Microsoft 单元测试框架。 测试资源管理器还可以在具有测试资源管理器适配器的第三方单元测试框架中运行测试。 有关更多信息,请参见如何:安装第三方单元测试框架。 |
|
说明 |
|
有关如何从命令行运行测试的信息,请参见演练:使用命令行测试实用工具。 |
准备演练
此时将显示"新建项目"对话框。
说明 |
如果名称"Bank"已被使用,请为该项目选择其他名称。 |
说明 |
如果代码编辑器中未打开 Class1.cs 文件,请在解决方案资源管理器中双击文件 Class1.cs 将其打开。 |
现在你有一个名为"Bank"的项目。 它包含要测试的源代码和用于对该源代码进行测试的工具。 Bank 的命名空间"BankAccountNS"包含公共类"BankAccount",在以下过程中将对该类的方法进行测试。
在本次快速入门中,我们重点关注 Debit 方法。Debit 方法是在从帐户提取资金时调用的,包含以下代码:
1 // method under test 2 public void Debit(double amount) 3 { 4 if(amount > m_balance) 5 { 6 throw new ArgumentOutOfRangeException("amount"); 7 } 8 if (amount < 0) 9 { 10 throw new ArgumentOutOfRangeException("amount"); 11 } 12 m_balance += amount; 13 }
系统必备:按照准备演练过程中的步骤执行操作。
创建单元测试项目
"BankTests"项目将添加到"Bank"解决方案。
在"解决方案资源管理器"中,选择"BankTests"项目中的"引用",然后从上下文菜单中选择"添加引用..."。
我们需要一个测试类来验证 BankAccount 类。 我们可以使用由项目模板生成的 UnitTest1.cs,但在对文件和类命名时应更具描述性。 通过重命名"解决方案资源管理器"中的文件,我们就可以一步完成这个操作。
重命名类文件
在"解决方案资源管理器"中,从 BankTests 项目中选择 UnitTest1.cs 文件。 从上下文菜单中,选择"重命名",然后将该文件重命名为 BankAccountTests.cs。 当显示对话框询问你是否希望重命名项目中对代码元素"UnitTest1"的所有引用时,选择"是"。 此步骤会将类的名称更改为 BankAccountTest。
BankAccountTests.cs 文件现包含下列代码:
1 // unit test code 2 using System; 3 using Microsoft.VisualStudio.TestTools.UnitTesting; 4 namespace BankTests 5 { 6 [TestClass] 7 public class BankAccountTests 8 { 9 [TestMethod] 10 public void TestMethod1() 11 { 12 } 13 } 14 }
向所测试项目添加 using 语句
我们还可以向类中添加 using 语句以供所测试项目调用,而无需使用完全限定名。 在类文件顶部添加:
1 using BankAccountNS
测试类要求
对测试类的最低要求如下:
单元测试项目中可以具有不含 [TestClass] 特性的其他类,测试类中可以具有不含 [TestMethod] 特性的其他方法。 可以在测试方法中使用这些其他的类和方法。
在此过程中,我们将编写单元测试方法以验证 BankAccount 类的 Debit 方法的行为。 方法如上所列。
通过分析所测试的方法,我们确定至少需要检查三个行为:
首次测试时,我们会验证一个有效金额(大于零且小于帐户余额)是否能从帐户提取正确金额。
创建测试方法
1 // unit test code 2 [TestMethod] 3 public void Debit_WithValidAmount_UpdatesBalance() 4 { 5 // arrange 6 double beginningBalance = 11.99; 7 double debitAmount = 4.55; 8 double expected = 7.44; 9 BankAccount account = new BankAccount("Mr. Bryan Walton",beginningBalance); 10 // act 11 account.Debit(debitAmount); 12 // assert 13 double actual = account.Balance; 14 Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly"); 15 }
该方法非常简单。 我们设置了有期初余额的新 BankAccount 对象,然后提取有效金额。 使用托管代码 AreEqual 方法的 Microsoft 单元测试框架来验证期末余额是否符合我们的预期。
测试方法要求
测试方法必须满足以下要求:
生成并运行测试
如果没有错误,会显示"单元测试资源管理器"窗口,其中"Debit_WithValidAmount_UpdatesBalance"在"未运行的测试"组中列出。 如果"测试资源管理器"在成功生成后未显示,请在菜单上选择"测试",再选择"窗口",然后选择"测试资源管理器"。
分析测试结果
测试结果包含描述失败的消息。 对于 AreEquals 方法,消息会显示你预期的内容(预期<XXX>参数)以及实际接收到的内容(实际<YYY>参数)。 我们本来预期余额会比期初余额越来越少,但却增加了取款金额。
复查 Debit 代码后发现,单元测试成功找到 bug。 取款金额本应从帐户余额中减去,结果却增加到帐户余额中。
更正 bug
若要更正错误,只需将代码行
1 m_balance += amount;
替换为
1 m_balance -= amount;
重新运行测试
在"测试资源管理器"中,选择"全部运行"以重新运行测试。 红色/绿色栏变为绿色,测试移动到"已通过的测试"组。
本部分介绍了分析的迭代过程、单元测试开发和重构如何帮助你增加成品代码的可靠性和有效性。
分析问题
在创建测试方法确认使用 Debit 方法正确扣除有效金额后,我们可以转而考察原始分析中的其余情况:
创建测试方法
首次尝试创建测试方法以解决这些问题的做法看似可行:
C# //unit test method [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange() { // arrange double beginningBalance = 11.99; double debitAmount = -100.00; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance); // act account.Debit(debitAmount); // assert is handled by ExpectedException }
我们使用 ExpectedExceptionAttribute 特性去断言正确的异常已抛出。 除非 ArgumentOutOfRangeException 已抛出,否则该特性将导致测试失败。 以正负 debitAmount 值运行测试,然后将所测试的方法暂时修改为在金额小于零时抛出泛型 ApplicationException,可证明测试行为正确。 若要测试提取金额大于余额的情况,只需执行以下操作即可:
运行测试
以不同的 debitAmount 值运行两种方法可证明测试能够恰当处理其余的情况。 运行所有三种测试可确认原始分析中的所有情况已正确覆盖。
继续分析
但是,最后两个测试方法还是有点麻烦。 其中任一测试运行时,我们都无法确定所测试代码中的哪个条件会抛出异常。 如果能区分这两种条件,也许会有帮助。 我们再深入思考此问题,就会了然 - 得知违反的条件会增加我们对测试的信心。 对于处理所测试方法抛出的异常的生产机制,该信息也非常可能带来帮助。 在方法抛出异常时生成更多信息将有助于解决所有问题,但是 ExpectedException 特性无法提供此信息。
再次查看所测试的方法,就会看到两个条件语句都使用 ArgumentOutOfRangeException 构造函数,该函数使用变量名称作为参数:
1 throw new ArgumentOutOfRangeException("amount");
通过搜索 MSDN 库,我们发现存在报告信息非常丰富的构造函数。 #ctor (String, Object, String) 包括变量名称、变量值和用户定义的消息。 我们可以重构所测试的方法,以使用此构造函数。 更理想的做法是使用公开的类型成员来指定错误。
重构所测试的代码
我们首先为类范围内的错误消息定义两个常量:
1 // class under test 2 public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance"; 3 4 public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero";
然后修改 Debit 方法中的两个条件语句:
1 // method under test 2 // ... 3 if (amount > m_balance) 4 { 5 throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage); 6 } 7 if (amount < 0) 8 { 9 throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage); 10 } 11 // ...
重构测试方法
在我们的测试方法中,首先移除 ExpectedException 特性。 在其位置上,我们捕获抛出的异常并验证其是否在正确的条件语句中抛出。 但是,我们现在必须在两个选项之间作出决定,以验证其余的条件。 例如,在 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 方法中,可以执行下列操作之一:
使用 Microsoft 单元测试框架中的 StringAssert.Contains 方法无需进行第一个选项所要求的计算,即可验证第二个选项。
修改 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 的第二次尝试可能类似于:
1 [TestMethod] 2 public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange() 3 { 4 // arrange 5 double beginningBalance = 11.99; 6 double debitAmount = 20.0; 7 BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\ 8 // act 9 try 10 { 11 account.Debit(debitAmount); 12 } 13 catch (ArgumentOutOfRangeException e) 14 { 15 // assert 16 StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage); 17 } 18 }
重测、重写和重新分析
我们使用不同的值重新对测试方法进行测试时,遇到以下情况:
第三种情况是测试方法中的 bug。 为了尝试解决该问题,我们在测试方法末尾添加了 Fail 断言,以处理未抛出异常的情况。
但重新测试表明,如果捕获到正确的异常,测试现将失败。 catch 语句重置异常,而方法继续执行,因而在新断言处失败。 为了解决这个新问题,我们在 StringAssert 之后添加 return 语句。 重新测试后证明问题得以解决。 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 的最终版本如下:
1 [TestMethod] 2 public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange() 3 { 4 // arrange 5 double beginningBalance = 11.99; 6 double debitAmount = 20.0; 7 BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\ 8 // act 9 try 10 { 11 account.Debit(debitAmount); 12 } 13 catch (ArgumentOutOfRangeException e) 14 { 15 // assert 16 StringAssert.Contains(e.Message, BankAccount.DebitAmountExceedsBalanceMessage); 17 return; 18 } 19 Assert.Fail("No exception was thrown.") 20 }
在最后部分,我们为改进测试代码而做出的努力最终提高了测试方法的可靠性和信息量。 但更重要的是,额外的分析也改善了所测试项目中的代码。
标签:
原文地址:http://www.cnblogs.com/BMFramework/p/4522267.html