本文介绍如何在 Visual Studio 中对通用 Windows 平台(UWP)应用进行单元测试。 Visual Studio 提供适用于 C#、Visual Basic 和 C++的 UWP 单元测试项目模板。 有关开发 UWP 应用的详细信息,请参阅 UWP 应用入门。
本文逐步讲解如何在 UWP 应用中创建和单元测试 C# 类。 该示例使用 体验驱动开发 来编写验证特定行为的测试,然后编写通过测试的代码。
创建并运行单元测试项目
以下过程介绍如何为 UWP 应用创建和运行单元测试项目。
创建 UWP 单元测试项目
在 Visual Studio “开始” 窗口中,选择 “创建新项目”。
在“ 创建新项目 ”页上,在“搜索”框中输入 单元测试 。 模板列表将筛选出单元测试项目。
为 C# 或 Visual Basic 选择正确的 UWP 单元测试模板,然后选择“ 下一步”。
从 Visual Studio 2022 版本 17.14 开始,适用于 C# 和 .NET 9 的建议单元测试模板是 UWP 单元测试应用 ,面向本机 AOT。 较旧的 UWP 模板名为 UWP 单元测试应用(.NET Native)和单元测试应用(通用 Windows)。
(可选)更改项目或解决方案名称和位置,然后选择“ 创建”。
(可选)更改目标版本和最低平台版本,然后选择“ 确定”。
Visual Studio 将创建测试项目,并在 Visual Studio 解决方案资源管理器中打开它。
在 Visual Studio “开始” 窗口中,选择 “创建新项目”。
在“ 创建新项目 ”页上,在“搜索”框中输入 单元测试 。 模板列表将筛选出单元测试项目。
选择 C# 或 Visual Basic 的 单元测试应用(通用 Windows) 模板,然后选择“ 下一步”。
(可选)更改项目或解决方案名称和位置,然后选择“ 创建”。
(可选)更改目标版本和最低平台版本,然后选择“ 确定”。
Visual Studio 将创建测试项目,并在 Visual Studio 解决方案资源管理器中打开它。
编辑项目的应用程序清单
在 解决方案资源管理器中,右键单击 Package.appxmanifest 文件,然后选择“ 打开”。
在清单设计器中,选择“ 功能 ”选项卡。
在 “功能 ”列表中,选择代码和单元测试所需的功能。 例如,如果代码及其单元测试需要访问 Internet,请选中 Internet 复选框。
仅选择单元测试正常运行所需的功能。
添加代码以对 UWP 应用进行单元测试
在 Visual Studio 代码编辑器中,编辑单元测试代码文件以添加测试所需的断言和逻辑。 有关示例,请参阅本文后面的 单元测试 C# 类 。
使用测试资源管理器运行单元测试
使用 测试资源管理器生成解决方案并运行单元测试。
在 Visual Studio 测试 菜单上,选择 “测试资源管理器”。 此时会打开 “测试资源管理器” 窗口。
在 测试资源管理器中,选择“ 运行所有 ”图标。 必须使用“全部运行”来发现 UWP 项目中的测试。
解决方案生成,单元测试运行。 测试运行后,测试将显示在 测试资源管理器 测试列表中,其中包含有关结果和持续时间的信息。
在 测试资源管理器中,还可以选择单个测试,然后右键单击以 运行 或 调试 测试,或 转到测试 以打开测试代码。 在顶部菜单中,你可以对测试进行分组、将测试添加到播放列表或打开测试选项。
使用 测试资源管理器生成解决方案并运行单元测试。
在 Visual Studio 测试 菜单上,选择 “测试资源管理器”。 此时会打开 “测试资源管理器” 窗口。
在 测试资源管理器中,选择“ 运行所有 ”图标。 必须使用“全部运行”来发现 UWP 项目中的测试。
解决方案构建完成,单元测试开始运行。 测试运行后,测试将显示在 测试资源管理器 测试列表中,其中包含有关结果和持续时间的信息。
在 测试资源管理器中,还可以选择单个测试,然后右键单击以 运行 或 调试 测试,或 转到测试 以打开测试代码。 在顶部菜单中,你可以对测试进行分组、将测试添加到播放列表或打开测试 选项。
对 C# 类进行单元测试
一组稳定的优良单元测试可保证你在更改代码时不会引入 Bug。 以下示例逐步讲解如何在 UWP 应用中为 C# 类创建单元测试。 该示例使用 体验驱动开发 编写验证特定行为的测试,然后编写通过测试的代码。
在 示例 Maths 代码项目中, Rooter 类实现一个函数,用于计算数字的估计平方根。 RooterTests 项目单元测试 Rooter 类。
创建解决方案和项目
创建 UWP 应用项目:
- 在 Visual Studio 文件 菜单上,选择“ 新建项目”。
- 在“ 创建新项目 ”页上,在“搜索”框中输入 空白应用 ,然后选择 C# 空白应用(通用 Windows) 项目模板。
- 在 “配置新项目 ”页上,将项目 命名为“数学”,然后选择“ 创建”。
- (可选)更改目标版本和最低平台版本,然后选择“ 确定”。 Visual Studio 将创建项目并在 解决方案资源管理器中打开它。
创建单元测试项目:
- 在 解决方案资源管理器中,右键单击 “数学” 解决方案,然后选择“ 添加新>项目”。
- 在 “添加新项目 ”页上,在“搜索”框中输入 单元测试 ,然后选择 C# 单元测试应用(通用 Windows) 项目模板。
- 将测试项目 命名为 RooterTests,然后选择“ 创建”。
- (可选)更改目标版本和最低平台版本,然后选择“ 确定”。 RooterTests 项目显示在解决方案资源管理器中的“数学”解决方案下。
验证测试是否在测试资源管理器中运行
该 Assert 类提供了几种静态方法,可用于验证测试方法的结果。
在解决方案资源管理器中,选择 RooterTests 项目中UnitTest.cs文件。
将以下代码插入到
TestMethod1
:[TestMethod] public void TestMethod1() { Assert.AreEqual(0, 0); }
在 测试资源管理器中,选择“ 运行所有测试”。
测试项目生成并运行,测试显示在 “已通过的测试”下。 右侧的“ 组摘要 ”窗格提供有关测试的详细信息。
将类添加到应用项目
在 解决方案资源管理器中,右键单击 “数学” 项目,然后选择“ 添加>类”。
将类文件 命名为Rooter.cs,然后选择“ 添加”。
在代码编辑器中,将以下代码添加到
Rooter
Rooter.cs文件中的类:public Rooter() { } // estimate the square root of a number public double SquareRoot(double x) { return 0.0; }
该
Rooter
类声明构造函数和SquareRoot
估算器方法。 该方法SquareRoot
是测试基本测试设置的最小实现。将
internal
关键字更改为public
Rooter
类声明中,以便测试代码可以访问它。public class Rooter
在 解决方案资源管理器中,右键单击 “数学” 项目,然后选择“ 添加>类”。
将类文件 命名为Rooter.cs,然后选择“ 添加”。
在代码编辑器中,将以下代码添加到
Rooter
Rooter.cs文件中的类:public Rooter() { } // estimate the square root of a number public double SquareRoot(double x) { return 0.0; }
该
Rooter
类声明构造函数和SquareRoot
估算器方法。 该方法SquareRoot
是测试基本测试设置的最小实现。将
public
关键字添加到Rooter
类声明,以便测试代码可以访问它。public class Rooter
将测试项目中的引用添加到应用项目
在 解决方案资源管理器中,右键单击 RooterTests 项目,然后选择“ 添加>引用”。
在 “引用管理器 - RooterTests ”对话框中,展开 “项目”,然后选择 “数学” 项目。
选择“确定”。
在
using Microsoft.VisualStudio.TestTools.UnitTesting;
行之后,将以下using
语句添加到UnitTest.cs:using Maths;
在 解决方案资源管理器中,右键单击 RooterTests 项目,然后选择“ 添加>引用”。
在 “引用管理器 - RooterTests ”对话框中,展开 “项目”,然后选择 “数学” 项目。
选择“确定”。
将以下
using
语句添加到using Microsoft.VisualStudio.TestTools.UnitTesting;
行后的 UnitTest.cs 中:using Maths;
添加使用应用函数的测试
将以下测试方法添加到 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); }
新测试显示在解决方案资源管理器和测试资源管理器的“未运行测试”节点中。
为了避免“有效负载包含两个或多个具有相同目标路径的文件”错误,在解决方案资源管理器中,展开“数学”项目下的“属性”节点,并删除 Default.rd.xml 文件。
保存所有文件。
运行测试
在 测试资源管理器中,选择“ 运行所有测试 ”图标。 解决方案生成,测试运行并通过。
在 测试资源管理器中,选择“ 运行所有测试 ”图标。 解决方案生成,测试运行并通过。
如果在运行测试时出现重复实体错误,请从测试项目中删除运行时指令文件 Properties\Default.rd.xml
,然后重新尝试。
你已设置测试和应用项目,并已验证是否可以在应用项目中运行调用函数的测试。 现在可以编写真正的测试和代码。
添加测试并使它们通过
最好不要更改已通过的测试。 相反,请添加新测试。 通过一次添加一个测试来开发代码,并确保所有测试在每次迭代后都通过。
添加新的测试
RangeTest
到UnitTest.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); } }
运行 RangeTest 测试并验证它是否失败。
小窍门
在测试驱动开发中,编写测试后立即运行测试。 这种做法有助于避免编写永不失败的测试的简单错误。
修复应用代码,使新测试通过。 在 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; }
在 测试资源管理器中,选择“ 运行所有测试 ”图标。 所有三个测试现在都通过。
在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); } }
运行 RangeTest 测试并验证它是否失败。
小窍门
在测试驱动开发中,编写测试后应立即运行测试。 这种做法有助于避免编写永不失败的测试的简单错误。
修复应用代码,使新测试通过。 在 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; }
在 测试资源管理器中,选择“ 运行所有测试 ”图标。 所有三个测试现在都通过。
重构代码
在本部分中,你将重构应用和测试代码,然后重新运行测试以确保它们仍然通过。
简化平方根估算
在 Rooter.cs中,通过更改以下行来简化函数中的
SquareRoot
中心计算:estimate = estimate - (estimate * estimate - x) / (2 * estimate);
功能
estimate = (estimate + x/estimate) / 2.0;
运行所有测试以确保尚未引入回归。 测试应全部通过。
删除重复的测试代码
RangeTest
方法对传递给Assert方法的tolerance
变量的分母进行硬编码。 如果计划添加更多使用相同容差计算的测试,在多个地方使用硬编码数值会导致代码难以维护。 相反,可以将专用帮助程序方法添加到 UnitTest1
类以计算容差值,然后从中 RangeTest
调用该方法。
若要添加辅助方法,请在 UnitTest.cs:
将下列方法添加到
UnitTest1
类:private double ToleranceHelper(double expected) { return expected / 1000; }
在
RangeTest
中更改以下行:double tolerance = expected/1000;
功能
double tolerance = ToleranceHelper(expected);
运行 RangeTest 测试以确保它仍然通过。
小窍门
如果将帮助程序方法添加到测试类,并且不希望帮助程序方法显示在 测试资源管理器中的列表中,请不要将 TestMethodAttribute 属性添加到该方法。