对 .NET 测试的评估





5.00/5 (15投票s)
关于 .NET 测试多年来评估的启发性历程
目录
引言
到目前为止,我一直为开发社区撰写特定技术文章来构建应用程序。现在,我将转向最有价值的流程 - 测试。
在这个竞争激烈的世界里,任何软件公司都必须确保在整个生命周期中进行测试。这是为了监控流程和方法,以确保质量和卓越。
本文讨论了行业中软件(自动化)测试周期的评估,特别是在 .NET 技术平台中。
Types
在行业中,测试根据不同维度进行分类,如下所示:
测试级别
序号。 | 测试名称 | 由谁负责 |
---|---|---|
1. | 单元测试 | 开发人员;在编码阶段 |
2. | 集成测试 | 开发人员和构建团队;在应用程序集成阶段 |
3. | 系统测试 | 测试人员;在应用程序质量验证阶段 |
测试意图
序号。 | 测试名称 | 目的 |
---|---|---|
1. | 性能测试 | 确定应用程序及其相关系统资源或其执行速度的有效性。 |
2. | 负载测试 | 确定系统在正常和预期的峰值负载条件下的行为。 |
3. | 回归测试 | 验证应用程序更改,以确保旧程序在新的更改下仍然有效。 |
4. | 验收测试 | 由最终用户确定是否满足规范或合同的要求。 |
模式
项目级别模式
项目级别模式在上一节 类型 中有详细描述。
数据点模式
数据驱动单元测试是为数据源中的每一行重复运行的单元测试。
使用数据驱动单元测试的一个常见场景是使用多个输入值来测试 API。与其编写多个调用 API 的单元测试(每个测试都有新的一组输入),或者在单元测试中创建数组并使用循环代码,不如编写一个测试方法来执行 API。
您可以通过两种方式之一创建数据驱动单元测试:
- 使用“属性”窗口并为现有单元测试设置特定属性。
- 将测试编码为数据驱动单元测试。
要配置现有单元测试,请添加定义要使用的数据源、要访问数据的方式以及要使用其行作为输入的表的属性。下图所示:
[TestClass] public class TestClass { private TestContext m_testContext; public TestContext TestContext { get { return m_testContext; } set { m_testContext = value; } } [TestMethod] [DeploymentItem("TESTDB.MDB")] [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\"TESTDB.MDB\"", "Students", DataAccessMethod.Sequential)] public void TestMethod() { Console.WriteLine( "StudentID: {0}, Student LastName: {1}", TestContext.DataRow["StudentID"], TestContext.DataRow["LastName"] ); } }
单元测试
单元测试用于通过直接调用类的方法、传递适当的参数来测试其他源代码,然后,如果包含断言语句,它们可以测试生成的值与预期值是否匹配。单元测试方法存在于测试类中,这些类存储在源代码文件中。
单元测试基础知识
在业务背景下,成本因素驱动着单元测试在任何软件开发周期中的重要性。不仅从时间角度,而且从成本角度,尽早识别/修复错误至关重要。
单元测试的好处 - DEV 社区
由于测试需要快速、健壮、可读、专注、确定和独立,开发人员社区会发现这会改善应用程序代码的设计。
- 出色的测试 - 对生产代码设计的影响
- 快速 - 基于简短/快速反馈的简洁方法
- 健壮 - 设计对更改开放
- 可读 - 更好的 API
- 专注 - 每个类和方法将有一个明确定义的责任
- 确定性 - 设计将代码与环境影响解耦
- 独立 - 设计不依赖于共享或静态状态
单元测试的业务优势
- 投放市场 - 对产品稳定性充满信心
- 确保核心构建块完好无损
- 提高产品技术合规性的可见性
- 高品质的产品吸引客户群
.NET 测试
概述
当单元测试是软件开发工作流的组成部分时,它对代码质量的影响最大。一旦编写了函数或其他应用程序代码块,就创建单元测试,以验证代码在标准、边界和不正确输入数据情况下的行为,并检查代码所做的任何显式或隐式假设。
通过测试驱动开发,开发人员在编写代码之前创建单元测试,因此他们将单元测试用作设计文档和功能规范。
排名前 5 的框架
序号。 | 框架 | 描述 |
---|---|---|
1. | MSTest | 作为 Visual Studio 单元测试框架的一部分,该框架还附带命令行工具。 |
2. | NUnit | 包括 GUI、命令行,与 ReSharper 集成到 Visual Studio 中。 |
3. | Pex | Microsoft Research 项目,提供 .NET 的白盒测试,使用 Z3 约束求解器生成单元测试输入。 |
4. | XUnit | 开源、以社区为中心的 .NET Framework 单元测试工具。它支持 C#、F#、VB.NET 和其他 .NET 语言;与 ReSharper、CodeRush、TestDriven.NET 和 Xamarin 配合使用。 |
5. | Gallilo | 可扩展、中立的自动化平台,提供通用对象模型、运行时服务和工具(如测试运行器),可供任意数量的测试框架使用。 |
世代
基于 Microsoft .NET Framework 和单元测试框架的演进,我个人将其分为三代:
- Gen X - 手动
- Gen Y - 自动存根/骨架
- Gen Z - 全自动化
以图示方式表示:
用例
为了说明单元测试的三代及其相关流程,下面以一个简单的用例来说明:
本例以个人储蓄账户及其相关操作(如存款和取款)为例。逻辑中精心添加了提款前余额非零等相应业务条件。
GenX
就我的情况而言,GenX 代表手动从头开始创建单元测试类和方法。这是在 .NET 框架中构建的初始模型。先决条件是:
- 每个测试类都标记有 [TestClass()] 属性。
- 每个单元测试都是一个测试方法,它被标记为 [TestMethod()] 属性。
- 断言语句用于将生成的值与预期值进行测试。
当生成单元测试时,会自动分配这些属性;当手动编写单元测试时,您必须自己添加类和方法属性。
实现
.NET GenX 单元测试阶段包含三个部分,即:
- Arrange(准备):设置运行被测代码所需的一切。这包括任何依赖项、模拟和测试运行所需数据的初始化。
- Act(执行):调用被测代码。
- Assert(断言):指定测试的通过标准,如果不满足则失败。
上述步骤用一个简单的测试方法来说明:
[TestMethod] public void ValidateZeroBalance() { /// 1. Arrange PersonalAccount account = new PersonalAccount(); /// 2. Act var result = account.GetBalance(); /// 3. Assert Assert.AreEqual(0, result); }
这种模式非常易读且一致。很容易识别测试的不同部分,并从根测试创建其他场景的测试。
代码示例
在 GenX 代码示例中,我们根据给定的用例创建了 PersonalSavingsAccount 类。为了方便起见,主类位于一个单独的项目中,与 Test Class - PersonalSavingsAccountTests 不同。
由于主类和测试类之间存在单独的层,项目二进制文件被引用到测试类中以供将来使用,以构建测试场景和条件,如下所示:
单元测试类 - PersonalSavingsAccountTests 是根据 GenX 模型手动从头开始编写的。
每个测试条件在 GenX 测试框架的每个 TestMethod 中都有详细说明。
[TestClass] public class PersonalSavingsAccountTests { [TestMethod()] public void PersonalSavingsAccountTest() { PersonalSavingsAccount account = new PersonalSavingsAccount("Ganesan Senthilvel", 900.90); } [TestMethod()] public void WithdrawSuccessTest() { PersonalSavingsAccount account = new PersonalSavingsAccount("Ganesan Senthilvel", 10000.11); account.Withdraw(500.50); } [TestMethod()] public void WithdrawFailZeroTest() { PersonalSavingsAccount account = new PersonalSavingsAccount("Ganesan Senthilvel", 150.50); account.Withdraw(0); } [TestMethod()] public void WithdrawFailBalanceTest() { PersonalSavingsAccount account = new PersonalSavingsAccount("Ganesan Senthilvel", 150.50); account.Withdraw(250.25); } [TestMethod()] public void DepositSuccessTest() { PersonalSavingsAccount account = new PersonalSavingsAccount("Ganesan Senthilvel", 10000.10); account.Deposit(200.20); } [TestMethod()] public void DepositFailZeroTest() { PersonalSavingsAccount account = new PersonalSavingsAccount("Ganesan Senthilvel", 100.10); account.Deposit(0); } }
单元测试结果 - Test Explorer
有三种方法可以验证单元测试是否通过:
- 使用一个或多个断言语句来验证特定结果。
- 验证未抛出异常。仍然建议使用一个或多个断言语句。
- 验证是否抛出了特定异常。您可以通过使用 ExpectedExceptionAttribute 属性来实现。
在我们的示例中,单元测试执行的结果如附件所示,列在 Test Explorer 中。
Test Explorer 解释了各种测试条件及其源代码、异常时的堆栈跟踪等。它将为开发社区提供软件测试的完整图景。
GenY
GenY 利用框架,该框架允许平台从您的应用程序代码生成单元测试存根。它消除了常规的测试创建任务,让开发人员可以专注于最高价值的工作,即编写测试本身。
常用的 GenY 工具之一是 Unit Test Generator 扩展 - Visual Studio Gallery。它通过减少创建新单元测试所需的设置工作来帮助提高开发人员的生产力。
实现
步骤 1:工具安装
安装后,您将在“工具”菜单下的“扩展和更新”中找到该扩展。
步骤 2:工具用法
右键单击您的方法以选择“生成单元测试”。
步骤 3:单元测试创建
Visual Studio Unit Test Generator 提供了生成和配置测试项目、测试类和测试存根的功能,使您能够更快地编写测试。它提供了一组配置选项,允许您根据您的命名和组织方案来定制生成。
该框架也可完全配置,以支持 MSTest、XUnit 和 NUnit,以便您可以选择最适合您环境的框架。
代码示例
运行之前的过程 - 步骤 3:单元测试创建后,Unit Test Generator 框架分析标记的类 - PersonalSavingsAccount,并自动生成相应测试类的存根。
在我们个人账户的用例中,GenY 支持开发人员通过上下文菜单创建单元测试,这样开发人员就不必担心设置测试项目和文件。以下是我们给定用例的自动生成的测试存根类。
[TestClass()] public class PersonalSavingsAccountTests { [TestMethod()] public void PersonalSavingsAccountTest() { Assert.Fail(); } [TestMethod()] public void WithdrawTest() { Assert.Fail(); } [TestMethod()] public void DepositTest() { Assert.Fail(); } } }
除了我们的流程,Unit Test Generator 框架还会生成一个测试项目和(如果需要)一个测试类,然后添加引用、命名空间和测试方法。
这是一个非常有用的扩展,它不仅增加了已被移除的功能,还通过增加对不同测试框架的支持来扩展功能。
限制是它不支持为内部类和方法生成测试。如果修复此问题并向 AssemblyInfo 添加 InternalsVisibileTo 属性,应该很容易实现。
GenZ
GenZ 是最高级别的单元测试自动化。作为上个月 Visual Studio 2015 发布的一部分,引入了 IntelliTest 来自动创建单元测试,以实现最大的代码覆盖率。
Pex 是一个 Microsoft Research 项目,它通过基于动态符号执行的自动化探索性测试,从手动编写的参数化单元测试生成单元测试。Pex 是 Visual Studio 2015 中 Gen-Z IntelliTest 框架的灵感来源。
在传统的单元测试套件中,每个测试用例代表一个示例使用场景,断言体现了输入和输出之间的关系。验证几个这样的场景可能就足够了,但经验丰富的开发人员知道,即使在经过充分测试的代码中也潜藏着错误,当正确但未经测试的输入引起错误响应时。
实现
IntelliTest 会探索您的 .NET 代码以生成测试数据和一系列单元测试。对于代码中的每个语句,都会生成一个将执行该语句的测试输入。对代码中的每个条件分支都会进行情况分析。
在 IntelliTest 框架可用后,Visual Studio 会启用两个菜单选项,如下所示:
- 创建 IntelliTest
- 运行 IntelliTest
“运行 IntelliTest”命令实际上会为指定类中的所有方法生成单元测试并继续执行。运行 IntelliTest 时,API 会在内部被调用,以指导测试数据生成、指定被测代码的正确性属性,并指导对被测代码的探索。
如果您想为任何特定方法生成,甚至从一个新方法开始,您可以使用“创建 IntelliTest”选项。
代码示例
处理完成后,Visual Studio 将启动一个名为“IntelliTest 探索结果”的新窗口,该窗口会根据方法参数生成一套所有可能的单元测试,并可以覆盖给定代码块的最大代码路径。这些生成的测试涵盖了参数值的所有可能组合;包括边界条件。
如下快照所示,生成的方法及其目标以及参数(a,b)的数量以及用于测试参数的值。
如果您从 IntelliTest 窗口中选择任何这些目标方法,您将能够看到 Pex 生成的单元测试的详细信息。这显示了内部如何调用生成的单元测试并覆盖代码的特定百分比。
让单元测试套件与快速发展的代码库保持同步是一个挑战。手动修改大量单元测试的代码会产生高昂的成本。IntelliTest 自动管理生成的单元测试套件,有助于解决这一挑战。
结论
在与各种开发者社区交流后,他们认为单元测试流程是他们的“眼中钉”,尽管存在已知的业务和项目优势。
作为我分析的总结,扼杀开发者的两个关键因素是:
- 开发周期中的耗时过程
- .NET 项目中构建单元测试类的未知流程/方法
希望本文能提示 .NET 范式中单元测试的便捷性,并鼓励他们更广泛地采用。
历史
初始版本