演练:为 UWP 应用创建和运行单元测试

本文介绍如何在 Visual Studio 中对通用 Windows 平台(UWP)应用进行单元测试。 Visual Studio 提供适用于 C#、Visual Basic 和 C++的 UWP 单元测试项目模板。 有关开发 UWP 应用的详细信息,请参阅 UWP 应用入门

本文逐步讲解如何在 UWP 应用中创建和单元测试 C# 类。 该示例使用 体验驱动开发 来编写验证特定行为的测试,然后编写通过测试的代码。

创建并运行单元测试项目

以下过程介绍如何为 UWP 应用创建和运行单元测试项目。

创建 UWP 单元测试项目

  1. 在 Visual Studio “开始” 窗口中,选择 “创建新项目”。

  2. 在“ 创建新项目 ”页上,在“搜索”框中输入 单元测试 。 模板列表将筛选出单元测试项目。

  3. 为 C# 或 Visual Basic 选择正确的 UWP 单元测试模板,然后选择“ 下一步”。

    从 Visual Studio 2022 版本 17.14 开始,适用于 C# 和 .NET 9 的建议单元测试模板是 UWP 单元测试应用 ,面向本机 AOT。 较旧的 UWP 模板名为 UWP 单元测试应用(.NET Native)单元测试应用(通用 Windows)。

    显示如何在 Visual Studio 中创建新的 UWP 单元测试应用的屏幕截图。

  4. (可选)更改项目或解决方案名称和位置,然后选择“ 创建”。

  5. (可选)更改目标版本和最低平台版本,然后选择“ 确定”。

Visual Studio 将创建测试项目,并在 Visual Studio 解决方案资源管理器中打开它。

显示解决方案资源管理器中的 UWP 单元测试项目的屏幕截图。

  1. 在 Visual Studio “开始” 窗口中,选择 “创建新项目”。

  2. 在“ 创建新项目 ”页上,在“搜索”框中输入 单元测试 。 模板列表将筛选出单元测试项目。

  3. 选择 C# 或 Visual Basic 的 单元测试应用(通用 Windows) 模板,然后选择“ 下一步”。

    显示如何在 Visual Studio 中创建新的 UWP 单元测试应用的屏幕截图。

  4. (可选)更改项目或解决方案名称和位置,然后选择“ 创建”。

  5. (可选)更改目标版本和最低平台版本,然后选择“ 确定”。

Visual Studio 将创建测试项目,并在 Visual Studio 解决方案资源管理器中打开它。

显示解决方案资源管理器中的 UWP 单元测试项目的屏幕截图。

编辑项目的应用程序清单

  1. 解决方案资源管理器中,右键单击 Package.appxmanifest 文件,然后选择“ 打开”。

  2. 在清单设计器中,选择“ 功能 ”选项卡。

  3. “功能 ”列表中,选择代码和单元测试所需的功能。 例如,如果代码及其单元测试需要访问 Internet,请选中 Internet 复选框。

仅选择单元测试正常运行所需的功能。

显示单元测试清单的屏幕截图。

显示单元测试清单的屏幕截图。

添加代码以对 UWP 应用进行单元测试

在 Visual Studio 代码编辑器中,编辑单元测试代码文件以添加测试所需的断言和逻辑。 有关示例,请参阅本文后面的 单元测试 C# 类

使用测试资源管理器运行单元测试

使用 测试资源管理器生成解决方案并运行单元测试。

  1. 在 Visual Studio 测试 菜单上,选择 “测试资源管理器”。 此时会打开 “测试资源管理器” 窗口。

  2. 测试资源管理器中,选择“ 运行所有 ”图标。 必须使用“全部运行”来发现 UWP 项目中的测试

    显示测试资源管理器“全部运行”图标的屏幕截图。

解决方案生成,单元测试运行。 测试运行后,测试将显示在 测试资源管理器 测试列表中,其中包含有关结果和持续时间的信息。

显示包含已完成测试信息的测试资源管理器的屏幕截图。

测试资源管理器中,还可以选择单个测试,然后右键单击以 运行调试 测试,或 转到测试 以打开测试代码。 在顶部菜单中,你可以对测试进行分组、将测试添加到播放列表或打开测试选项。

显示测试资源管理器上下文菜单的屏幕截图。

使用 测试资源管理器生成解决方案并运行单元测试。

  1. 在 Visual Studio 测试 菜单上,选择 “测试资源管理器”。 此时会打开 “测试资源管理器” 窗口。

  2. 测试资源管理器中,选择“ 运行所有 ”图标。 必须使用“全部运行”来发现 UWP 项目中的测试

    显示测试资源管理器“全部运行”图标的屏幕截图。

解决方案构建完成,单元测试开始运行。 测试运行后,测试将显示在 测试资源管理器 测试列表中,其中包含有关结果和持续时间的信息。

显示包含已完成测试信息的测试资源管理器的屏幕截图。

测试资源管理器中,还可以选择单个测试,然后右键单击以 运行调试 测试,或 转到测试 以打开测试代码。 在顶部菜单中,你可以对测试进行分组、将测试添加到播放列表或打开测试 选项

显示测试资源管理器上下文菜单的屏幕截图。

对 C# 类进行单元测试

一组稳定的优良单元测试可保证你在更改代码时不会引入 Bug。 以下示例逐步讲解如何在 UWP 应用中为 C# 类创建单元测试。 该示例使用 体验驱动开发 编写验证特定行为的测试,然后编写通过测试的代码。

示例 Maths 代码项目中, Rooter 类实现一个函数,用于计算数字的估计平方根。 RooterTests 项目单元测试 Rooter 类。

创建解决方案和项目

创建 UWP 应用项目:

  1. 在 Visual Studio 文件 菜单上,选择“ 新建项目”。
  2. 在“ 创建新项目 ”页上,在“搜索”框中输入 空白应用 ,然后选择 C# 空白应用(通用 Windows) 项目模板。
  3. “配置新项目 ”页上,将项目 命名为“数学”,然后选择“ 创建”。
  4. (可选)更改目标版本和最低平台版本,然后选择“ 确定”。 Visual Studio 将创建项目并在 解决方案资源管理器中打开它。

创建单元测试项目:

  1. 解决方案资源管理器中,右键单击 “数学” 解决方案,然后选择“ 添加新>项目”。
  2. “添加新项目 ”页上,在“搜索”框中输入 单元测试 ,然后选择 C# 单元测试应用(通用 Windows) 项目模板。
  3. 将测试项目 命名为 RooterTests,然后选择“ 创建”。
  4. (可选)更改目标版本和最低平台版本,然后选择“ 确定”。 RooterTests 项目显示在解决方案资源管理器中的“数学”解决方案下。

验证测试是否在测试资源管理器中运行

Assert 类提供了几种静态方法,可用于验证测试方法的结果。

  1. 解决方案资源管理器中,选择 RooterTests 项目中UnitTest.cs文件。

  2. 将以下代码插入到TestMethod1

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(0, 0);
    }
    
  3. 测试资源管理器中,选择“ 运行所有测试”。

  4. 测试项目生成并运行,测试显示在 “已通过的测试”下。 右侧的“ 组摘要 ”窗格提供有关测试的详细信息。

将类添加到应用项目

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

  2. 将类文件 命名为Rooter.cs,然后选择“ 添加”。

  3. 在代码编辑器中,将以下代码添加到RooterRooter.cs文件中的类:

    public Rooter()
    {
    }
    
    // estimate the square root of a number
    public double SquareRoot(double x)
    {
        return 0.0;
    }
    

    Rooter 类声明构造函数和 SquareRoot 估算器方法。 该方法 SquareRoot 是测试基本测试设置的最小实现。

  4. internal关键字更改为publicRooter类声明中,以便测试代码可以访问它。

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

  2. 将类文件 命名为Rooter.cs,然后选择“ 添加”。

  3. 在代码编辑器中,将以下代码添加到RooterRooter.cs文件中的类:

    public Rooter()
    {
    }
    
    // estimate the square root of a number
    public double SquareRoot(double x)
    {
        return 0.0;
    }
    

    Rooter 类声明构造函数和 SquareRoot 估算器方法。 该方法 SquareRoot 是测试基本测试设置的最小实现。

  4. public 关键字添加到 Rooter 类声明,以便测试代码可以访问它。

    public class Rooter
    

将测试项目中的引用添加到应用项目

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

  2. “引用管理器 - RooterTests ”对话框中,展开 “项目”,然后选择 “数学” 项目。

    截图展示如何为数学项目添加引用。

  3. 选择“确定”

  4. using Microsoft.VisualStudio.TestTools.UnitTesting;行之后,将以下using语句添加到UnitTest.cs

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

  2. “引用管理器 - RooterTests ”对话框中,展开 “项目”,然后选择 “数学” 项目。

    显示添加对 Maths 项目的引用的屏幕截图。

  3. 选择“确定”

  4. 将以下 using 语句添加到 using Microsoft.VisualStudio.TestTools.UnitTesting; 行后的 UnitTest.cs 中:

    using Maths;
    

添加使用应用函数的测试

  1. 将以下测试方法添加到 UnitTest.cs

    [TestMethod]
    public void BasicTest()
    {
        Maths.Rooter rooter = new Rooter();
        double expected = 0.0;
        double actual = rooter.SquareRoot(expected * expected);
        double tolerance = .001;
        Assert.AreEqual(expected, actual, tolerance);
    }
    

    新测试显示在解决方案资源管理器测试资源管理器“未运行测试”节点中。

  2. 为了避免“有效负载包含两个或多个具有相同目标路径的文件”错误,在解决方案资源管理器中,展开“数学”项目下的“属性”节点,并删除 Default.rd.xml 文件。

  3. 保存所有文件。

运行测试

测试资源管理器中,选择“ 运行所有测试 ”图标。 解决方案生成,测试运行并通过。

显示测试资源管理器中“基本测试”通过的屏幕截图

测试资源管理器中,选择“ 运行所有测试 ”图标。 解决方案生成,测试运行并通过。

显示测试资源管理器中通过的基本测试的屏幕截图

如果在运行测试时出现重复实体错误,请从测试项目中删除运行时指令文件 Properties\Default.rd.xml ,然后重新尝试。

你已设置测试和应用项目,并已验证是否可以在应用项目中运行调用函数的测试。 现在可以编写真正的测试和代码。

添加测试并使它们通过

最好不要更改已通过的测试。 相反,请添加新测试。 通过一次添加一个测试来开发代码,并确保所有测试在每次迭代后都通过。

  1. 添加新的测试RangeTestUnitTest.cs

    [TestMethod]
    public void RangeTest()
    {
        Rooter rooter = new Rooter();
        for (double v = 1e-6; v < 1e6; v = v * 3.2)
        {
            double expected = v;
            double actual = rooter.SquareRoot(v*v);
            double tolerance = expected/1000;
            Assert.AreEqual(expected, actual, tolerance);
        }
    }
    
  2. 运行 RangeTest 测试并验证它是否失败。

    显示测试资源管理器中失败的 RangeTest 的屏幕截图。

    小窍门

    在测试驱动开发中,编写测试后立即运行测试。 这种做法有助于避免编写永不失败的测试的简单错误。

  3. 修复应用代码,使新测试通过。 在 Rooter.cs中,按如下所示更改 SquareRoot 函数:

    public double SquareRoot(double x)
    {
        double estimate = x;
        double diff = x;
        while (diff > estimate / 1000)
        {
            double previousEstimate = estimate;
            estimate = estimate - (estimate * estimate - x) / (2 * estimate);
            diff = Math.Abs(previousEstimate - estimate);
        }
        return estimate;
    }
    
  4. 测试资源管理器中,选择“ 运行所有测试 ”图标。 所有三个测试现在都通过。

  1. UnitTest.cs中添加一个名为RangeTest的新测试:

    [TestMethod]
    public void RangeTest()
    {
        Rooter rooter = new Rooter();
        for (double v = 1e-6; v < 1e6; v = v * 3.2)
        {
            double expected = v;
            double actual = rooter.SquareRoot(v*v);
            double tolerance = expected/1000;
            Assert.AreEqual(expected, actual, tolerance);
        }
    }
    
  2. 运行 RangeTest 测试并验证它是否失败。

    显示测试资源管理器中失败的 RangeTest 的屏幕截图。

    小窍门

    在测试驱动开发中,编写测试后应立即运行测试。 这种做法有助于避免编写永不失败的测试的简单错误。

  3. 修复应用代码,使新测试通过。 在 Rooter.cs中,按如下所示更改 SquareRoot 函数:

    public double SquareRoot(double x)
    {
        double estimate = x;
        double diff = x;
        while (diff > estimate / 1000)
        {
            double previousEstimate = estimate;
            estimate = estimate - (estimate * estimate - x) / (2 * estimate);
            diff = Math.Abs(previousEstimate - estimate);
        }
        return estimate;
    }
    
  4. 测试资源管理器中,选择“ 运行所有测试 ”图标。 所有三个测试现在都通过。

重构代码

在本部分中,你将重构应用和测试代码,然后重新运行测试以确保它们仍然通过。

简化平方根估算

  1. Rooter.cs中,通过更改以下行来简化函数中的 SquareRoot 中心计算:

    estimate = estimate - (estimate * estimate - x) / (2 * estimate);

    功能

    estimate = (estimate + x/estimate) / 2.0;

  2. 运行所有测试以确保尚未引入回归。 测试应全部通过。

删除重复的测试代码

RangeTest方法对传递给Assert方法的tolerance变量的分母进行硬编码。 如果计划添加更多使用相同容差计算的测试,在多个地方使用硬编码数值会导致代码难以维护。 相反,可以将专用帮助程序方法添加到 UnitTest1 类以计算容差值,然后从中 RangeTest调用该方法。

若要添加辅助方法,请在 UnitTest.cs

  1. 将下列方法添加到 UnitTest1 类:

    private double ToleranceHelper(double expected)
    {
        return expected / 1000;
    }
    
  2. RangeTest中更改以下行:

    double tolerance = expected/1000;

    功能

    double tolerance = ToleranceHelper(expected);

  3. 运行 RangeTest 测试以确保它仍然通过。

小窍门

如果将帮助程序方法添加到测试类,并且不希望帮助程序方法显示在 测试资源管理器中的列表中,请不要将 TestMethodAttribute 属性添加到该方法。

后续步骤