单元测试

由于跨平台应用将在现实世界中使用,因此应该对其进行测试,以提高其质量、可靠性和性能。 应在应用上执行许多类型的测试,包括单元测试、集成测试和用户界面测试。 单元测试是最常见的测试形式,对于构建高质量的应用至关重要。

单元测试采用应用的一个较小单元(通常是某个方法),将其与其余代码相隔离,并验证其行为是否符合预期。 其目标是检查每个功能单元是否按预期执行,以便错误不会在整个应用中传播。 在发生 bug 的位置检测 bug,比在次要故障点位置间接观察 bug 造成的影响更为有效。

单元测试通常使用安排-行动-断言模式:

步骤 说明
排列 初始化对象,并设置传递给受测方法的数据的值。
行动 使用所需参数调用受测方法。
Assert 验证受测方法的操作行为是否符合预期。

此模式可确保单元测试可读、自描述和一致。

如果将单元测试规划为软件开发工作流不可或缺的一部分,则单元测试对代码质量的影响最为显著。 单元测试可以充当应用的设计文档和功能规范。 一旦编写了方法,就应该编写单元测试来验证该方法响应标准、边界和不正确输入数据情况的行为,并检查代码做出的任何显式或隐式假设。 或者,如果使用测试驱动开发,则会在代码之前编写单元测试。

重要

单元测试对回归非常有效。 也就是说,功能曾经正常工作,但被错误的更新所干扰。

xUnit 是建议用于 .NET MAUI 应用的测试框架。

将 xUnit 测试添加到 .NET MAUI 解决方案

若要将 xUnit 测试添加到 .NET MAUI 解决方案,请执行以下操作之一:

xUnit 测试项目的项目文件 (.csproj) 如以下示例所示

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="coverlet.collector" Version="6.0.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
    <PackageReference Include="xunit" Version="2.5.3" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="Xunit" />
  </ItemGroup>

</Project>

$(TargetFramework) 生成属性指定测试项目的目标框架。 此框架是计算机上安装的最新版 .NET。

xunit 包引入子包,其中包含测试框架本身,以及用于通过单元测试检测常见问题的 Roslyn 分析器。 在 Visual Studio 中以及通过 dotnet test 命令运行单元测试时需要 xunit.runner.visualstudioMicrosoft.NET.Test.Sdk 包。 coverlet.collector 包可用于收集代码覆盖率。 如果你不打算收集代码覆盖率,可以删除此包引用。 有关单元测试的代码覆盖率的详细信息,请参阅使用代码覆盖率进行单元测试

可通过两种主要方法为应用构建单元测试:

  1. 要进行单元测试的代码位于 .NET MAUI 类库项目中。
  2. 要进行单元测试的代码位于 .NET MAUI 应用项目中。

每种方法都需要特定的配置。

配置 .NET MAUI 类库项目以进行单元测试

使用此方法时,要进行单元测试的代码位于 .NET MAUI 应用项目使用的 .NET MAUI 类库项目中。 若要针对 .NET MAUI 类库编写单元测试,需要更新项目使用的目标框架。 为此,可将 xUnit 测试项目文件 (.csproj) 中 $(TargetFramework) 生成属性的值添加到 .NET MAUI 类库项目文件中的 $(TargetFrameworks) 生成属性

<TargetFrameworks>net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>

在此示例中,net8.0 的值已添加到 .NET MAUI 类库项目文件中的 $(TargetFrameworks) 生成属性。

然后,必须从 xUnit 测试项目添加对 .NET MAUI 类库项目的引用。

配置 .NET MAUI 应用项目以进行单元测试

使用此方法时,要进行单元测试的代码位于.NET MAUI 应用项目中。 若要针对 .NET MAUI 应用项目编写单元测试,需要更新项目使用的目标框架。 为此,可将 xUnit 测试项目文件 (.csproj) 中 $(TargetFramework) 生成属性的值添加到 .NET MAUI 应用项目文件中的 $(TargetFrameworks) 生成属性

<TargetFrameworks>net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>

在此示例中,net8.0 的值已添加到 .NET MAUI 应用项目文件中的 $(TargetFrameworks) 生成属性。

还需要修改 .NET MAUI 应用项目,使其不会输出 xUnit 测试项目使用的目标框架的可执行文件。 为此,可将一个条件添加到 .NET MAUI 应用项目文件中的 $(OutputType) 生成属性:

<OutputType Condition="'$(TargetFramework)' != 'net8.0'">Exe</OutputType>

在此示例中,仅当目标框架不是 net8.0 时,.NET MAUI 应用项目才会生成可执行文件。

然后,必须从 xUnit 测试项目添加对 .NET MAUI 应用项目的引用。

写入单元测试

xUnit 支持两种不同类型的单元测试:

测试类型 属性 说明
事实 Fact 始终为 true 的测试,用于测试固定条件。
理论 Theory 只对特定数据集成立的测试。

应将单元测试放在 xUnit 测试项目中,并使用 [Fact][Theory] 属性对其进行修饰。 以下示例演示了使用 [Fact] 属性的单元测试:

namespace MyUnitTests
{
    public class MyTests
    {
        [Fact]
        public void PassingTest()
        {
            Assert.AreEqual(4, 2+2);
        }

        [Fact]
        public void FailingTest()
        {
            Assert.AreEqual(5, 2+2);
        }
    }
}

在此示例中,测试代表有意通过和失败的测试。

以下示例演示了使用 [Theory] 属性的单元测试:

namespace MyUnitTests
{
    public class MyTests
    {
        [Theory]
        [InlineData(3)]
        [InlineData(4)]
        [InlineData(5)]
        public void MyTheoryTest(int value)
        {
            Assert.True(value % 2 == 1);
        }
    }
}

在此示例中,尽管只有一个测试方法,但实际上有三个测试,因为将对每个数据项运行理论测试一次。

提示

每个单元测试将测试一个操作。 随着测试复杂性的扩大,测试的验证变得更加困难。 通过将单元测试限制为单个关注点,能够确保测试可重复且独立,并可缩短执行时间。 有关详细信息,请参阅单元测试最佳做法

运行单元测试

单元测试可以在 Visual Studio 的测试资源管理器中运行,或者使用 dotnet test 命令来运行。 有关测试资源管理器的信息,请参阅使用测试资源管理器运行单元测试。 有关 dotnet test 命令的详细信息,请参阅在 .NET 中使用 dotnet test 和 xUnit 进行 C# 单元测试dotnet test

使用设备运行器运行单元测试

还可以使用设备运行器在设备上运行单元测试。 设备运行器是一个测试运行器应用,它提供了可以通过 XHarness 从 CLI 运行的视觉运行器 shell 和一些挂钩。 有关详细信息,请参阅测试设备运行器 wiki 中的文档。

另请参阅