简介
随着业务的革新和发展,运行它们的系统也需要进行更新。随业务的发展、革新以及与合作伙伴、客户和供应商的结合,这些系统将在复杂性方面持续扩增。 这种复杂性迫使 IT 的领导者们在开发过程中(即,在实现之前)确保质量。有一种方法可使开发人员减少进入 QA 环节的故障数量,即,针对自定义代码严格执行自动化单元测试。在开发过程中强制使用自动化单元测试可为团队成员提供有关如何使用自定义代码的示例(这些示例易于使用并自行记录)。 使用结构化、自动化单元测试面临的挑战之一是完成这些任务所需的代码总数。(测试代码需要使用大量代码!)代码生成的概念(简单定义为“创建软件的软件”)正随着时间的快速推移而逐渐深入到团队 IT 开发之中。有些人认为代码生成有助于缩短“推向市场”策略的时间,强制内部标准/协定,并促进开发过程。 Microsoft 认识到这一需要后提供了一个功能丰富、带有下一代开发平台 Visual Studio 2005 Team System (VSTS) 的代码生成引擎。本文提供针对单元测试代码生成的循序渐进的指导,并深入探讨如何在用例中使用。 vsts/UTFwVS05TmSys.mspx#top">
重新思考单元测试请考虑以下情况:您负责为公司生成下一代系统,同时您是较大的开发团队中的一员。您是 UI 开发人员,负责尽可能多地创建 Microsoft ASP.NET/Microsoft WinForms。您依赖“中间层”团队完成其中间层组件 — 这些组件用于执行数据库 CRUD (Create-Retrieve-Update-Delete) 以及与该系统中每个实体相关的业务规则。 经过几周的 UI 开发,您完成了窗体并且收到了中间层开发人员打算向您提交其类库的消息。下面提供一段对话示例,说明我们大多数人在开发过程中都会遇到的一些事情。
在考虑如何进行这样有趣的 项目之后,您打定了主意,决定检验中间层的单元测试套件。在深入钻研该代码之后,您注意到该窗体有两个未标记的文本框,以及三个标记为 button1、button2 和 button3 的按钮(幸运的话,它们将排列在窗体上)。接下来,在查看与这些按钮相关的事件之后,您认识到这些代码都未经注释,并且数据变量都被命名为 x、y、z。如果幸运,您还会注意到 button1 和 button2 执行该对象的 Save() 方法,而 button3 执行 Delete() 方法。执行时,您会接收到很多 System.Exception 错误,这是因为遗漏了很多配置设置。 这显然是一个特例,我希望多数开发团队不要进行这一试验,下面让我们看一下该方案中“单元测试”遇到的问题:
输入自动化单元测试xUnit 框架在 1998 年作为 eXtreme 编程的核心概念引入。它提出了一个有效的机制,有助于开发人员将结构化、有效且自动的单元测试添加常规开发活动中。从那以后,该框架演化为针对自动化单元测试框架的实际标准。 创建自动化单元测试的用例简单说,自动化单元测试是:
xUnit 框架元素表 2 分析 xUnit 框架以及对应于 Visual Studio 2005 Team System 的 Unit Testing Framework 等价物的基本概念。
测试装置示例请考虑以下针对 BankAccount 类的类关系图,以及一个示例测试装置 (BankAccountTests.cs)。 图 1. BankAccount 类 示例测试装置: BankAccountTests.csusing BankAccountDemo.Business; using Microsoft.VisualStudio.QualityTools.UnitTesting.Framework; namespace BankAccountDemo.Business.Tests { [TestClass()] public class BankAccountTest { [TestInitialize()] public void Initialize() { } [TestCleanup()] public void Cleanup() { } [TestMethod()] public void ConstructorTest() { float currentBalance = 500; BankAccount target = new BankAccount(currentBalance); Assert.AreEqual(currentBalance, target.CurrentBalance, "Balances are not equal upon creation"); } [TestMethod()] public void DepositMoneyTest() { float currentBalance = 500; BankAccount target = new BankAccount(currentBalance); float depositAmount = 10; target.DepositMoney(depositAmount); Assert.IsTrue( (currentBalance + depositAmount) > target.CurrentBalance, "Deposit not applied correctly"); } [TestMethod()] public void MakePaymentTest() { float currentBalance = 500; BankAccount target = new BankAccount(currentBalance); float paymentAmount = 250; target.MakePayment(paymentAmount); Assert.IsTrue(currentBalance - paymentAmount == target.CurrentBalance, "Payment not applied correctly"); } } } 主单元测试概念 == 断言用于该形式单元测试的主要概念是,自动化单元测试是基于“断言”的,即可定义为“事实或您相信为事实的内容”。从逻辑角度看,请考虑该语句“when I do {x}, I expect {y} as a result”。 这可以轻松地翻译为代码,方法是使用 Microsoft.VisualStudio.QualityTools.UnitTesting.Framework 命名空间中可用的三个“断言”类中的任一个:Assert、StringAssert 和 CollectionAssert。主类 Assert 提供用于测试基础条件语句的断言。StringAssert 类自定义了在使用字符串变量时有用的断言。同样,CollectionAssert 类包括在使用对象集合时有用的断言方法。 表 3 显示可用于当前版本 Unit Testing Framework 的断言。
这些自动化单元测试用什么运行?正如前面提到的,xUnit 框架将“测试运行器”的概念定义为应用程序负责:(a) 执行单元测试;(b) 报告测试结果。对于本文,包含 Visual Studio 2005 Team System (VSTS) 的 Unit Testing 引擎作为我们的“测试运行器”。图 2 表示 BankAccountTests.cs 类的执行结果。 图 2. 测试结果窗格:单元测试执行结果 Microsoft Visual Studio 2005 使用源项目的代码模型动态填充该视图。它基于该源代码中的自定义属性动态发现有关该测试套件的信息。表 4 表示最常见的单元测试属性(以及执行的次序)。
我编写什么类型的测试?一个方法及其相关测试之间很难有一对一关系。编写自动化单元测试需要开发人员“进行全面思考”,并了解关于对象的所有内容 — 它将如何消耗、使用、处理,以及在任何情况下如何起到积极、消极、非决定性作用。 例如,请考虑一个用于针对数据库中 Customer 项执行 CRUD(创建、检索、更新、删除)功能的典型对象方法。对于该对象的 Load() 方法,要针对以下方案编写测试:
这些只是自动化单元测试套件许多用法中的一部分。我曾经听说一个小团队使用单元测试查看针对其组件的已知安全攻击。从宏观的角度来看,单元测试应该明确保证组件的正常使用。具有丰富的测试集将使团队确信您已经准确实现了既定的目标:编写有效的软件。无论自信源自哪里 — 这就是您需要编写的测试。 您测试什么?从本质上看,这些自动化单元测试非常低级。它们旨在测试下至构造函数、方法调用的对象,甚至是对象上的属性。 关于“公共对私有”的主题在单元测试派系中引发了许多争论。许多人认为单元测试只应该测试对象的公共接口。其他人认为应该测试每个调用 — 包括内部私有方法。VSTS 支持两个单元测试级别。VSTS 通过使用私有访问器或包装类支持私有测试,后者提供基于“私有”方法和属性生成单元测试的功能。 为什么生成代码?阅读上面的列表后,您可能会想起前面项目的单个对象,并思考:“如果我用“这些”对象进行该操作,就需要编写大量代码!”请考虑开发人员仍编写“单元测试”代码的事实 — 只在不同的窗体(例如,前面提到的 WinForms 示例)上进行。此外,具有可自行记录、可重用的实现示例带来的好处远大于生成更多代码所带来的麻烦。最后,在单元测试中设计更多的环节已证明可以减少质量保证环节中的故障。 正如前面所提到的,代码生成是“软件创建软件”的过程。基于可重复的过程创建代码是理想的。例如,一些使用代码生成的较好示例包括:脚本数据、创建表示实体及其在储存库(数据库 CRUD)中存在的对象,或者创建适用于数据维护的 UI 控件。使用代码生成的好处包括:
正如您所预期的,自动化单元测试属于“优秀代码生成候选者”。在现有组件中指出一些内容并针对这些自动单元测试生成初始代码难道不是很理想吗?而且不只是保留单元测试框架,还会围绕对象的公共接口生成实现示例吗?将来的 Visual Studio 2005 Team System 用户将拥有该功能以及更多功能! 让我们生成一些代码吧!本例中,我们将生成本文前面提到的 BankAccount 类的代码。本文的这一部分旨在为您介绍代码生成过程,并重点介绍所提供的功能以及从 VSTS 使用 Unit Testing 引擎的好处。 第 1 步:创建您的实现代码首先,我们创建一个将用作应用程序的业务层的类库项目。 要在 VS 2005 中创建该库:
VS 2005 创建该类后,下一个任务就是创建针对您的项目设计的 BankAccount 类。为此,需要执行以下操作:
using System; using System.Collections.Generic; using System.Text; namespace BankAccountDemo.Business { public class BankAccount { // Properties private float _currentBalance; public float CurrentBalance { get { return _currentBalance; } } // Constructors public BankAccount(float initialBalance) { this._currentBalance = initialBalance; } // Methods public void DepositMoney(float depositAmount) { this._currentBalance += depositAmount; } public void MakePayment(float paymentAmount) { this._currentBalance -= paymentAmount; } } } 第 2 步:生成您的初始单元测试代码由于 Unit Testing 引擎内置于 Visual Studio 2005 Team System,因此生成代码比以前更容易。除了生成单元测试结构之外,它将生成特定于实例的信息,例如,对象创建、类型化参数和方法执行。 VS 2005 提供在任何类结构级别生成单元测试代码的功能,这些级别包括命名空间、类、方法、属性、构造函数,等等。可通过右键单击这些代码元素并单击 Generate test(s)(图 3)进行此操作。 图 3. Generate test(s) 方法 因此,要开始代码生成过程,请执行以下步骤:
现在,应该为您提供 Generate Unit Tests 对话框(如图 4 所示)。该对话框及其组件提供对该过程中生成的代码进行自定义的功能。让我们看一下所有这些元素。 图 4. 生成 Unit Tests 对话框 Current selection: 树视图允许导航自定义类及其元素。VS 2005 使用反射填充该树视图,并在右键单击以及单击 Create Tests 的位置自动选择组件。图 3 中,由于我在类级别进行了此操作,因此该对话框自动选择用于代码生成的所有类元素。如果选择在单个级别(即,构造函数、属性或方法)进行生成,则只选择那些元素。 Filter 选项(位于右上角)提供修改树视图(图 5)中所示结果的功能,包括显示非公共项、基类型以及“只属于我的代码”。如果使用的是大型解决方案,或者感觉显示私有的内部结构会弄乱选择窗口,那么这对您很有益处。 图 5. 筛选选择结果 下一个是 Output project 对话框(位于 Current selection: 树视图下)。该列表框允许您针对生成的测试装置选择目的项目(图 6)。如果您的解决方案包含以前创建的测试项目,则将包含该测试项目以供选择。由于这是我们首次访问该对话框,因此可以选择 Create a new … Test Project 选项。 图 6. 输出项目选择 要继续我们的过程,请执行以下操作:
最后,该对话框提供通过 Settings 按钮(位于左下角)自定义代码生成过程的功能。单击该按钮将加载 Test Generation Settings 对话框,如图 7 所示。 图 7. Test Generation Settings 对话框 该对话框允许您进行以下更改:
要完成我们的配置并生成单元测试代码(以及更多),请执行以下操作:
VS 2005 将显示一个进度栏,提供代码生成过程中的状态。该过程将在几秒钟内完成,您可以看到一个名为 BankAccountTest.cs 的类。 生成了什么?在我们对该测试装置进行特别查看之前,让我们看一下在代码生成过程中创建了什么。 首先,它创建了 Test Class Library 项目 BankAccountDemo.Business.Test。请注意该项目如何包含对实现类 BankAccountDemo.Business(您从其中生成代码)和 Microsoft.VisualStudio.QualityTools.UnitTestFramework 类库的引用。在查看该类的内容时,您将注意到以下文件:
由 Unit Testing 引擎生成的类包括以下组件:
让我们仔细看一下 DepositMoneyTest(),它负责确保当前的平衡能反映原始数量与累计数量的总和。 /// ///A test case for DepositMoney (float) /// [TestMethod()] public void DepositMoneyTest() { float initialBalance = 0; // TODO: Initialize to an appropriate value BankAccount target = new BankAccount(initialBalance); float depositAmt = 0; // TODO: Initialize to an appropriate value target.DepositMoney(depositAmt); Assert.Inconclusive("A method that does not return a value" + "cannot be verified."); } 请注意该生成引擎除创建一个 stub TestMethod() 对象外,是如何进行其他操作的。它创建了适用于接口的示例单元测试,包括:
生成后:我现在需要做什么?考虑要完成相同的操作可以不必做哪些事情,则通常可以认识到代码生成的好处。在我们的示例中,我们不必:
由于代码生成过程创建了特定于对象接口的示例单元测试,因此我们接近于初始测试的完成阶段了。通常情况下,只需“填充空白”并完成断言(一个或多个),方法是将“已知的数据值”分配给属性变量并创建适当的 Assert() 方法。显然,这不是针对所有测试的示例,特别是对具有多个断言的复杂测试而言。 只需几秒钟的时间(使用相对较少的击键),您就能够将生成的单元测试代码转换为这些实际的测试。 例如,请考虑我们以如下方式开始。 [TestMethod()] public void DepositMoneyTest() { float initialBalance = 0; // TODO: Initialize to an appropriate value BankAccount target = new BankAccount(initialBalance); float depositAmt = 0; // TODO: Initialize to an appropriate value target.DepositMoney(depositAmt); Assert.Inconclusive("A method that does not return a value " + "cannot be verified."); } 我们能够完成相对容易且具有有限击键的测试(更改部分用黑体表示)。 [TestMethod()] public void DepositMoneyTest() { float currentBalance = 500; BankAccount target = new BankAccount(currentBalance); float depositAmt = 10; target.DepositMoney(depositAmt); Assert.AreEqual(currentBalance + depositAmt, target.CurrentBalance, "Deposit Test: Deposit not applied correctly"); } 重新生成单元测试代码好消息是,代码生成过程不会让您重写以前生成(和修改)的单元测试。使用 Visual Studio 2005 Team System 的 Beta 2 版本,代码生成选项提供一个启用/禁用创建已存在测试的复选框。如果选择它,而且该过程找到了一个具有相同名称的现有测试,则该过程将忽略该测试方法,并创建后续测试,从而将一个数字附加到该方法名的末尾。这通常在对象中使用重载的方法或构造函数时发生,或者当单击 Generate 按钮而不取消选定现有测试时发生。 自动化单元测试建议虽然本节可以独立成文,但这里只是一些您在创建单元测试时可以采纳的基本建议。
小结自动化单元测试为开发环节提供了一个结构化、自行纪录、高度便携且可重复的过程。如果在搜索现有程序集,或者如果开发环境需要在开始开发之前进行完整的设计,则请考虑使用内置到 Microsoft Visual Studio 2005 Team System 中的代码生成引擎。Visual Studio 2005 Team System 的单元测试代码生成功能可以为您节省宝贵的时间,而且有助于强制团队的开发标准和约定。通过生成用于自动化单元测试的基本内容,包括生成带有对象创建的测试方法、参数变量和基断言类,您应该能够顺利地在您的开发方法论中采用自动化单元测试。 |