演练:创建并运行托管代码的单元测试
本演练将创建将引导您逐步完成,运行,并且,自定义一系列单元测试使用 Microsoft 单元测试托管代码的结构,Visual Studio 测试管理器。 您将从正处于开发过程中的 C# 项目开始,创建执行该项目代码的测试,运行测试并检查结果。 然后,可以更改项目代码并重新运行测试。
本主题包含以下各节:
备注
本演练使用 Microsoft 单元测试托管代码的结构。测试资源管理器还可以从运行单元测试框架具有 Test 资源管理器适配器的第三方测试。有关更多信息,请参阅如何:安装第三方单元测试框架。
备注
有关如何从命令行运行测试的信息,请参见演练:使用命令行测试实用工具。
系统必备
- bank 项目。 请参见用于创建单元测试的示例项目。
准备演练
准备演练
打开 Visual Studio 2012。
在**“文件”菜单上指向“新建”,然后单击“项目”**。
此时将出现“新建项目”对话框。
在**“已安装的模板”下单击“Visual C#”**。
在应用程序类型的列表中单击**“类库”**。
在**“名称”框中键入 Bank,然后单击“确定”**。
备注
如果名称“Bank”已被使用,请为该项目选择其他名称。
将创建新的 Bank 项目并将其显示在解决方案资源管理器中,而且将在代码编辑器中打开 Class1.cs 文件。
备注
如果代码编辑器中未打开 Class1.cs 文件,请在解决方案资源管理器中双击文件 Class1.cs 将其打开。
从用于创建单元测试的示例项目中复制源代码。
用用于创建单元测试的示例项目中的代码替换 Class1.cs 的原始内容。
该文件保存为 BankAccount.cs
在**“生成”菜单上,单击“生成解决方案”**。
现在您有一个名为“Bank”的项目。 它包含要测试的源代码和用于对该源代码进行测试的工具。 Bank 的命名空间**“BankAccountNS”包含公共类“BankAccount”**,在以下过程中将对该类的方法进行测试。
本快速启动,我们讨论 Debit 方法。,在提取货币帐户并包含以下代码时,debit 方法调用:
// method under test
public void Debit(double amount)
{
if(amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount");
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount");
}
m_balance += amount;
}
创建单元测试项目
系统必备:按照准备演练过程中的步骤执行操作。
创建单元测试项目
在 文件 菜单中,选择 添加,然后选择 新项目…。
在新项"对话框中,展开 已安装,展开 Visual C#,然后选择 测试。
从模板列表,选择 单元测试项目。
在 名称 框中,输入 BankTest,然后选择 确定。
BankTests 项目添加到 银行 解决方案。
在 BankTests 项目中,添加对 银行 解决方案。
在解决方案资源管理器中,BankTests 项目的 SELECT 引用 从上下文菜单中选择 添加引用 ...。
在引用管理器对话框中,展开 解决方案 然后检查 银行 项目。
创建测试选件类
我们需要验证的 BankAccount 选件类测试选件类。 我们可以使用由项目模板生成的 UnitTest1.cs,但是,我们应为文件和类更具声明性的名称。 我们可以通过对解决方案资源管理器中重命名文件执行于在一个步骤。
对选件类重命名文件
在解决方案资源管理器中,选择在 BankTests 项目的 UnitTest1.cs 文件。 从上下文菜单中,选择 重命名,然后将该文件重命名为 BankAccountTests.cs。 选中询问的对话框的 是 您是否在项目中重命名对代码元素“UnitTest1”。 此换步选件类的名称。BankAccountTest。
BankAccountTests.cs 文件现在包含以下代码:
// unit test code
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace BankTests
{
[TestClass]
public class BankAccountTests
{
[TestMethod]
public void TestMethod1()
{
}
}
}
添加 using 语句添加到项目测试
我们还可以添加 using 语句添加到选件类并将到项目测试,而无需使用完全限定名。 在选件类文件的顶部,添加:
using BankAccountNS
测试选件类要求
测试选件类的最低要求如下:
[TestClass] 属性在 Microsoft 单元测试框架所需的为托管代码对于包含单元测试方法要运行的测试资源管理器的任何选件类。
每个测试需要测试管理器运行必须具有 [TestMethod]属性的方法。
可以让没有 [TestClass] 属性在部件的其他选件类测试项目,并且,可以让其他方法测试没有 [TestMethod] 属性的选件类。 可以使用这些其他选件类,并在方法的测试方法。
创建第一个测试方法
在此过程中,我们将编写单元测试方法验证 BankAccount 选件类的 Debit 方法的行为。 方法上面列出。
通过分析方法下测试,我们确定需要签出至少有三种行为:
方法引发 [ArgumentOutOfRangeException],如果超出信用保存与该平衡大。
如果超出信用保存小于零,小于它还引发 ArgumentOutOfRangeException。
如果在 1.) 和 2. 的检查) 满意,方法从帐户余额减去此数量。
在我们第一个测试,我们验证是否比帐户余额小于,并且大于零) 的有效的量 (一提取从该帐户的正确数量。
创建测试方法
添加 using BankAccountNS; 语句添加到 BankAccountTests.cs 文件。
将下面的方法添加到该 BankAccountTests 选件类:
// unit test code [TestMethod] public void Debit_WithValidAmount_UpdatesBalance() { // arrange double beginningBalance = 11.99; double debitAmount = 4.55; double expected = 7.44; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance); // act account.Debit(debitAmount); // assert double actual = account.Balance; Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly"); }
方法非常简单。 我们设置与开头平衡的新 BankAccount 对象并提取有效的数量。 我们使用 Microsoft 单元测试托管代码 AreEqual 方案的结构可以验证期末余额什么是我们需要。
测试方法要求
测试方法必须满足以下要求:
必须用 [TestMethod] 特性修饰方法。
该方法必须返回 void。
方法不能有参数。
生成并运行测试
生成并运行测试
在**“生成”菜单上,选择“生成解决方案”**。
如果没有错误,UnitTestExplorer 窗口中。Debit_WithValidAmount_UpdatesBalance 在 未运行的测试 组中列出。 如果测试资源管理器中未出现,请在生成成功,在菜单上选择后的 测试,则选择 窗口,然后选择 测试资源管理器。
选择 全部运行 运行测试。 在测试运行状态栏在窗口顶部的进行动画处理。 在测试结束时请运行,条变为绿色,如果所有测试方法通过,或红色,如果有任何测试失败。
在这种情况下,测试失败。 测试方法移到 未通过的测试。 引用。 选择方法测试资源管理器来查看详细信息在窗口的底部。
修复代码并重新运行测试
分析测试结果
测试结果包含描述错误的消息。 为 AreEquals 方法,则会显示您预期内容的 (**Expected<entity_PLACEHOLDERXXX>**参数),以及实际接收 ( Actual<YYY> 参数)。 我们需要从开始拒绝该平衡的平衡,而是由量取款增加而增加。
debit 代码的复审表示,单元测试在发现 bug 成功。 则应当减去时,量取款添加到帐户余额它。
更正 bug
若要更正此错误,请替换行
m_balance += amount;
with
m_balance -= amount;
重新运行测试
在测试资源管理器中,选择 全部运行 重新运行测试。 红色-绿色条启用绿色,并且,测试移到 通过的测试 组。
使用单元测试以提高代码
本节描述迭代过程如何分析,单元测试,开发,并重构可帮助您的生产代码更加可靠和有效。
分析问题
在创建测试方法后确认有效的量 Debit 方法都将正确扣除,可以转向在我们的原始分析的其余的情况:
方法引发 ArgumentOutOfRangeException,如果超出信用保存与该平衡大。
如果超出信用保存小于零,小于它还引发 ArgumentOutOfRangeException。
创建测试方法
在创建测试方法的第一次尝试解决这些问题是有为:
//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,该数量小于零演示是测试能否正常工作的更小时。 若要测试用例,则会提取的此数量小于该平衡时大,我们需要执行的所有:
创建新测试方法命名 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange。
将 Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange 的方法主体。新方法。
与该平衡设置 debitAmount 为数字大。
运行测试
运行不同的值的两个方法 debitAmount 的说明,是否足以测试处理我们剩余的大小写。 运行所有三种测试确认在我们的原始分析的全部大小写正确地介绍。
继续该分析
但是,最后两个测试方法还一些令人焦虑。 我们无法确定在代码来适应下测试,则引发任何测试运行时。 区分两个条件某种方法很有用。 我们更考虑该问题,变得显而易见知道哪个条件违反了将增加我们中的 confidence 测试。 它由方法引发测试时,此信息也可能对该生成的结构很有用的异常。 生成更多信息,则方法将引发是帮助所有问题,但是,ExpectedException 属性无法提供此信息。
查看方法下再次测试,我们同时看到条件语句使用接受参数的名称作为参数的一个 ArgumentOutOfRangeException 构造函数:
throw new ArgumentOutOfRangeException("amount");
从 MSDN Library 中搜索,我们查看存在构造函数报告丰富的信息。 ArgumentOutOfRangeException(String, Object, String) 包括参数、参数值和一个用户定义的消息的名称。 我们可以重构方法下测试使用此构造函数。 更好,可以使用公开的类型成员指定错误。
测试代码的重构
我们首先定义错误消息的两个常数。选件类范围:
// class under test
public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero";
我们然后修改在 Debit 方法的两个条件语句:
// method under test
// ...
if (amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}
// ...
重构测试方法
在我们测试方法中,我们首先移除 ExpectedException 属性。 在原地,我们捕获时引发的异常并验证它是否能在正确的条件语句是否引发了。 但是,我们现在必须决定在两个选项卡之间验证我们剩余的情况。 例如在 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 方法,可以执行下列操作之一:
断言异常 ( ArgumentOutOfRangeException 构造函数的第二个参数的 ActualValue 属性) 的开头平衡大。 此选项要求我们测试异常的 ActualValue 属性的测试方法 beginningBalance 变量,并需要然后验证 ActualValue 大于零。
断言消息 (构造函数的第三个参数) 在 BankAccount 选件类包括定义的 DebitAmountExceedsBalanceMessage。
Microsoft 单元测试的 StringAssert.Contains 方法测试框架使我们验证第二个选项,而无需第一个选项的计算。
在修改 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 的第二个尝试可能类似于:
[TestMethod]
public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\
// act
try
{
account.Debit(debitAmount);
}
catch (ArgumentOutOfRangeException e)
{
// assert
StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
}
}
重测,复盖,并重新分析
我们再次测试不同的值时的测试方法,我们会遇到以下情况:
如果我们查看与该平衡大的正确的错误使用 debitAmount,Contains 断言通过,异常,将忽略,所以测试方法传递。 这是我们需的行为。
如果要使用 debitAmount,断言失败,因为错误的错误返回。 如果要在方法引入一个临时 ArgumentOutOfRange 异常在另一个点测试代码路径,断言还会失败。 这也是好。
如果 debitAmount 值是有效的 (即,小于平衡,但大于零,不捕获到异常,因此,断言它不会被捕获。 测试方法传递。 这不是好,因为我们希望测试方法失败,如果未引发异常。
第三种情况是在我们的 bug 测试方法。 若要尝试解决该问题,我们添加 Fail 断言在测试方法结束时处理不会引发异常的情况。
但是,重新测试,表示测试现在未通过,如果正确的捕获到异常。 catch 语句重置异常,而方法继续执行,会在新的 assert。 若要解决新的问题,我们在 StringAssert之后添加一个 return 语句。 再次测试来确认我们解决了问题。 我们的 Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange 的定稿如下所示:
[TestMethod]
public void Debit_WhenAmountIsGreaterThanBalance_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);\
// act
try
{
account.Debit(debitAmount);
}
catch (ArgumentOutOfRangeException e)
{
// assert
StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
return;
}
Assert.Fail("No exception was thrown.")
}
在此最后部分,工作完成改进我们测试代码生成更加可靠和信息性测试方法。 但更重要的是,多余的分析也会改进。我们的项目的测试代码。