单元测试基础知识

通过创建和运行单元测试来检查代码是否按预期工作。 它称为单元测试,因为你将程序的功能分解为可以作为单个 单元进行测试的离散可测试行为。 Visual Studio 测试资源管理器提供了一种灵活且高效的方法来运行单元测试并在 Visual Studio 中查看其结果。 Visual Studio 为托管代码和本机代码安装Microsoft单元测试框架。 使用 单元测试框架 创建单元测试、运行它们并报告这些测试的结果。 更改代码时重新运行单元测试,以测试代码是否仍然正常工作。 Visual Studio Enterprise 可以使用 Live Unit Testing 自动执行此作,该测试可检测受代码更改影响的测试,并在键入时在后台运行这些测试。

当单元测试是软件开发工作流不可或缺的一部分时,单元测试对代码的质量产生最大的影响。 编写函数或其他应用程序代码块后,立即创建单元测试,以验证代码的行为以响应输入数据的标准、边界和不正确的事例,并检查代码做出的任何显式或隐式假设。 使用 体验驱动开发,在编写代码之前创建单元测试,以便将单元测试用作设计文档和功能规范。

测试资源管理器还可以运行已实现测试资源管理器加载项接口的第三方和开源单元测试框架。 可以通过 Visual Studio 扩展管理器和 Visual Studio 库添加其中许多框架。 有关详细信息,请参阅 安装第三方单元测试框架

可以从代码快速生成测试项目和测试方法,或根据需要手动创建测试。 使用 IntelliTest 浏览 .NET 代码时,可以生成测试数据和单元测试套件。 对于代码中的每个语句,将生成将执行该语句的测试输入。 了解如何 为 .NET 代码生成单元测试

开始

有关直接进行编码的单元测试简介,请参阅以下文章之一:

银行解决方案示例

在本文中,我们将使用名为 的虚构应用程序 MyBank 的开发作为示例。 您无需具体代码即可理解本文中的说明。 测试方法以 C# 编写,并使用适用于托管代码的 Microsoft 单元测试框架呈现。 但是,概念很容易转移到其他语言和框架。

MyBank 解决方案 2019

MyBank 解决方案 2022

我们首次尝试应用程序 MyBank 的设计包括一个帐户组件,该组件代表单个帐户及其与银行的交易,以及表示聚合和管理单个帐户的功能的数据库组件。

我们创建一个解决方案 Bank,其中包含两个项目:

  • Accounts

  • BankDB

我们第一次尝试设计 Accounts 项目包含一个类来保存有关帐户的基本信息、一个接口,该接口指定任何类型的帐户的通用功能,例如从帐户中存款和提取资产,以及从表示检查帐户的接口派生的类。 我们通过创建以下源文件来开始帐户项目:

  • AccountInfo.cs 定义帐户的基本信息。

  • IAccount.cs 定义帐户的标准 IAccount 接口,包括从帐户中存款和提取资产以及检索帐户余额的方法。

  • CheckingAccount.cs 包含 CheckingAccount 实现 IAccount 检查帐户接口的类。

我们从经验中知道,从支票帐户提取的一件事就是确保提取的金额小于帐户余额。 因此,我们使用检查此条件的方法IAccount.Withdraw替代CheckingAccount该方法。 该方法可能如下所示:

public void Withdraw(double amount)
{
    if(m_balance >= amount)
    {
        m_balance -= amount;
    }
    else
    {
        throw new ArgumentException(nameof(amount), "Withdrawal exceeds balance!");
    }
}

现在我们已经有了一些代码,是时候进行测试了。

使用 Copilot 创建单元测试

还可以使用 Copilot /tests 斜杠命令从代码生成单元测试。 例如,可以键入 /tests using NUnit Framework 以生成 NUnit 测试。 有关详细信息,请参阅 Copilot Chat 中使用斜杠命令

创建单元测试项目和测试方法 (C#)

对于 C#,从代码生成单元测试项目和单元测试存根通常更为快捷。 也可以选择根据要求手动创建单元测试项目和测试。 如果要使用第三方框架从代码创建单元测试,则需要安装以下扩展之一: NUnitxUnit。 如果不使用 C#,请跳过本部分,然后 手动创建单元测试项目和单元测试

生成单元测试项目和单元测试存根

  1. 在代码编辑器窗口中,右键单击并选择从右键单击菜单中选择 “创建单元测试 ”。

    在编辑器窗口中,查看上下文菜单

    注释

    “创建单元测试”菜单命令仅适用于 C# 代码。 若要将此方法用于 .NET Core 或 .NET Standard,需要 Visual Studio 2019 或更高版本。

    在编辑器窗口中,查看上下文菜单

    注释

    “创建单元测试”菜单命令仅适用于 C# 代码。 若要将此方法用于 .NET Core 或 .NET Standard,需要 Visual Studio 2019 或更高版本。

  2. 选择 “确定 ”以接受默认设置以创建单元测试,或更改用于创建和命名单元测试项目和单元测试的值。 可以选择默认添加到单元测试方法的代码。

    Visual Studio 中的“创建单元测试”对话框

    Visual Studio 中的“创建单元测试”对话框

  3. 单元测试存根是在类中的所有方法的新单元测试项目中创建的。

    创建单元测试

    创建单元测试

  4. 现在,请继续学习如何 编写测试 以使单元测试有意义,以及可能需要添加的任何额外的单元测试以全面测试代码。

手动创建单元测试项目和单元测试

单元测试项目通常镜像单个代码项目的结构。 在 MyBank 示例中,将添加两个名为 AccountsTestsBankDbTests 解决方案的 Bank 单元测试项目。 测试项目名称是任意的,但采用标准命名约定是个好主意。

若要将单元测试项目添加到解决方案,请执行以下作:

  1. 解决方案资源管理器中,右键单击解决方案,然后选择“添加新>项目”。

  2. 在项目模板搜索框中键入 测试 ,查找要使用的测试框架的单元测试项目模板。 (在本文中的示例中,我们使用 MSTest。)

  3. 在下一页上,将项目命名。 若要测试 Accounts 示例的项目,可以命名项目 AccountsTests

  4. 在您的单元测试项目中,添加对正在测试的代码项目的引用,在我们的示例中,是指 Accounts 项目。

    若要创建对代码项目的引用,请执行以下步骤:

    1. 在解决方案资源管理器中的单元测试项目中,右键单击 “引用 ”或“ 依赖项 ”节点,然后选择“ 添加项目引用 ”或 “添加引用”(以可用为准)。

    2. “引用管理器 ”对话框中,打开 “解决方案 ”节点,然后选择“ 项目”。 选择代码项目名称并关闭对话框。

每个单元测试项目都包含反映代码项目中类名称的类。 在我们的示例中, AccountsTests 项目将包含以下类:

  • AccountInfoTests类包含项目中类AccountInfoAccounts的单元测试方法

  • CheckingAccountTests 类包含类的 CheckingAccount 单元测试方法。

编写测试

你使用的单元测试框架和 Visual Studio IntelliSense 将指导你为代码项目的单元测试编写代码。 若要在 测试资源管理器中运行,大多数框架都需要添加特定属性来标识单元测试方法。 框架还提供了一种方法(通常通过断言语句或方法属性),以指示测试方法是否已通过或失败。 其他属性标识可选的设置方法,这些方法在类初始化时执行,并且每个测试方法运行前和销毁类之前,还有在每个测试方法后运行的拆卸方法。

AAA (Arrange, Act, Assert) 模式是编写测试方法单元测试的一种常见方法。

  • 单元测试方法的 Arrange 节初始化对象,并设置传递给所测试方法的数据的值。

  • Act 节使用排列的参数调用受测方法。

  • Assert 部分验证所测试方法是否符合预期地运行。 对于 .NET,类中 Assert 的方法通常用于验证。

若要测试 CheckingAccount.Withdraw 示例的方法,可以编写两个测试:一个测试验证方法的标准行为,一个验证超过余额的取款将失败(以下代码显示了 .NET.中支持的 MSTest 单元测试)。 在 CheckingAccountTests 类中,我们添加以下方法:

[TestMethod]
public void Withdraw_ValidAmount_ChangesBalance()
{
    // arrange
    double currentBalance = 10.0;
    double withdrawal = 1.0;
    double expected = 9.0;
    var account = new CheckingAccount("JohnDoe", currentBalance);

    // act
    account.Withdraw(withdrawal);

    // assert
    Assert.AreEqual(expected, account.Balance);
}

[TestMethod]
public void Withdraw_AmountMoreThanBalance_Throws()
{
    // arrange
    var account = new CheckingAccount("John Doe", 10.0);

    // act and assert
    Assert.ThrowsException<System.ArgumentException>(() => account.Withdraw(20.0));
}

有关Microsoft单元测试框架的详细信息,请参阅以下文章之一:

为单元测试设定超时

如果使用的是 MSTest 框架,则可以使用该 TimeoutAttribute 框架对单个测试方法设置超时:

[TestMethod]
[Timeout(2000)]  // Milliseconds
public void My_Test()
{ ...
}

将超时设置为允许的最大值:

[TestMethod]
[Timeout(TestTimeout.Infinite)]  // Milliseconds
public void My_Test ()
{ ...
}

在测试资源管理器中运行测试

生成测试项目时,测试将显示在 测试资源管理器中。 如果 测试资源管理器 不可见,请在 Visual Studio 菜单上选择 “测试 ”,选择 “Windows”,然后选择 “测试资源管理器 ”(或按 Ctrl + ET)。

单元测试资源管理器

单元测试资源管理器

运行、写入和重新运行测试时, 测试资源管理器 可以在 失败的测试通过的测试跳过的测试 和非 运行测试组中显示结果。 可以通过工具栏中的选项选择不同的组。

还可以通过在全局级别的搜索框中匹配文本或选择其中一个预定义的筛选器来筛选任何视图中的测试。 可以随时运行任何一组测试。 测试运行的结果会立即显现在资源管理器窗口顶部的通过/失败栏中。 选择测试时,将显示测试方法结果的详细信息。

运行和查看测试

“测试资源管理器”工具栏可帮助你发现、组织和运行感兴趣的测试。

从“测试资源管理器”工具栏运行测试

从“测试资源管理器”工具栏运行测试

可以选择“ 全部 运行”以运行所有测试(或按 Ctrl + RV),或选择 “运行 ”以选择要运行的测试子集(Ctrl + RT)。 选择一个测试,在测试详细信息窗格中查看该测试的详细信息。 从右键单击菜单(键盘:F12)中选择“打开测试”以显示所选测试的源代码。

如果各个测试没有阻止其按任意顺序运行的依赖项,请在工具栏的设置菜单中启用并行测试执行。 这可以显著减少运行所有测试所需的时间。

每次构建后运行测试

若要在每个本地生成后运行单元测试,请在“测试资源管理器”工具栏中打开设置图标,然后选择“ 生成后运行测试”。

筛选和分组测试列表

当具有大量测试时,可以在 “测试资源管理器” 搜索框中键入以按指定字符串筛选列表。 可以通过从筛选器列表中选择来更加限制您的筛选器事件。

搜索筛选器类别

搜索筛选器类别

Button Description
“测试资源管理器”组按钮 若要按类别对测试进行分组,请选择“ 分组依据 ”按钮。

有关详细信息,请参阅 使用测试资源管理器运行单元测试

问答

问:如何调试单元测试?

使用 测试资源管理器 启动用于测试的调试会话。 使用 Visual Studio 调试器单步调试您的代码,可以顺畅地来回跳转于单元测试和被测项目之间。 若要开始调试,请执行以下操作:

  1. 在 Visual Studio 编辑器中,在要调试的一个或多个测试方法中设置断点。

    注释

    由于测试方法可以按任意顺序运行,因此请在要调试的所有测试方法中设置断点。

  2. 测试资源管理器中,选择测试方法,然后从快捷菜单中选择 “调试所选测试 ”。

详细了解 如何调试单元测试

问:如果我使用的是 TDD,如何从测试生成代码?

A: 使用快速操作在项目代码中生成类和方法。 在调用要生成的类或方法的测试方法中编写语句,然后打开错误下显示的灯泡。 如果调用的是新类的构造函数,请从菜单中选择 “生成类型 ”,然后按照向导在代码项目中插入该类。 如果调用是一个方法,请从 IntelliSense 菜单中选择 生成方法

生成方法桩快速操作菜单

生成方法存根快速操作菜单

问:是否可以创建将多个数据集作为输入来运行测试的单元测试?

答: 是的。 借助数据驱动测试方法 ,可以使用单个单元测试方法测试一系列值。 请对测试方法使用 `DataRow`, `DynamicData` 或 `DataSource` 属性,这些属性指定包含要测试的变量值的数据源。

特性化方法针对数据源中的每个行运行一次。 如果任一迭代失败,测试资源管理器将报告方法的测试失败。 方法的测试结果详细信息窗格显示每行数据的通过/失败状态。

详细了解 数据驱动的单元测试

问:是否可以查看我的单元测试覆盖了多少代码?

答: 是的。 可以使用 Visual Studio 中的 Visual Studio Code 覆盖率工具来确定单元测试实际测试的代码量。 支持本机语言和托管语言,以及所有可以由单元测试框架运行的单元测试框架。

答: 是的。 可以使用 Visual Studio Enterprise 中的 Visual Studio Code 覆盖率工具来确定单元测试实际测试的代码量。 支持本地和托管语言,以及所有可以由单元测试框架运行的单元测试框架。

可以在所选测试或解决方案中的所有测试上运行代码覆盖率。 “ 代码覆盖率结果 ”窗口显示按行、函数、类、命名空间和模块执行的产品代码块的百分比。

若要在解决方案中运行测试方法的代码覆盖率,请选择“ 测试>分析所有测试的代码覆盖率”。

覆盖率结果显示在 “代码覆盖率结果 ”窗口中。

代码覆盖率结果

代码覆盖率结果

详细了解 代码覆盖率

问:是否可以在具有外部依赖项的代码中测试方法?

答: 是的。 如果你有 Visual Studio Enterprise,Microsoft Fakes 可与使用托管代码单元测试框架编写的测试方法一起使用。

Microsoft Fakes 使用两种方法为外部依赖项创建替换类:

  1. 存根生成从目标依赖类的父接口派生的替代类。 存根方法可以替代目标类的公共虚拟方法。

  2. 使用运行时检测将对非虚拟方法的调用转移到替代桩方法。

在这两种方法中,均使用生成的调用委托来指定测试方法中所需的依赖方法的行为。

详细了解如何使用 Microsoft Fakes 隔离单元测试方法

问:是否可以使用其他单元测试框架来创建单元测试?

一个: 是的,请按照以下步骤 查找并安装其他框架。 重启 Visual Studio 后,重新打开解决方案以创建单元测试,然后在此处选择已安装的框架:

选择其他已安装的单元测试框架

将使用所选框架创建单元测试存根。

问:如何导出单元测试结果?

答: 可以使用 .runsettings 文件通过命令行或 Visual Studio IDE 配置单元测试,并指定测试结果文件。 有关详细信息,请参阅 LoggerRunSettings 元素