练习 - 单元测试 Azure Functions
单元测试是敏捷方法的基本部分。 Visual Studio 提供测试项目模板。 使用此模板为应用程序创建单元测试,可将相同的技术应用于 Azure Functions 测试。
在奢侈手表联机网站方案中,开发团队制定了一项策略,即在单元测试中实现至少 80% 的代码覆盖率。 你想要为 Azure Functions 实现相同的策略。
在此处,你将了解如何通过 Visual Studio 使用 xUnit
测试框架来测试 Azure Functions。
创建单元测试项目
第一步是创建一个包含单元测试的项目,并将其添加到包含 Azure 函数应用的解决方案中。 使用以下步骤创建单元测试项目以测试 WatchInfo 函数。
在 Visual Studio 的“解决方案资源管理器”窗口中,右键单击“WatchPortalFunction”解决方案,选择“添加”,然后选择“新建项目”。
在“添加新项目”窗口中,向下滚动,选择“xUnit 测试项目”C#+ 图标模板,然后选择“下一步”。
显示“配置新项目”窗口。 在“项目名称”字段中,输入“WatchFunctionsTests”。 选择“位置”字段旁边的浏览图标,然后选择“WatchPortalFunction”文件夹。
选择“下一页”。 将显示“其他信息”窗口。
在“目标框架”下。 接受“.NET 6.0 (长期支持)”的默认值。
选择创建。
添加项目后,右键单击“解决方案资源管理器”窗口中的“WatchFunctionTests”项目,然后选择“管理 NuGet 包”。
在“NuGet:WatchFunctionTests”窗口中,选择“浏览”选项卡。在“搜索”框中,输入“Microsoft.AspNetCore.Mvc”。 选择“Microsoft.AspNetCore.Mvc”包,然后选择“安装”。
注意
测试项目将创建一个模拟 HTTP 环境。 执行此操作所需的类位于 Microsoft.AspNetCore.Mvc 包中。
包安装期间,请等待。 如果出现“预览更改”消息框,请选择“确定”。 在“许可证接受”消息框中,选择“我接受”。
添加软件包后,在“解决方案资源管理器”窗口的“WatchFunctionsTests”项目下,右键单击“UnitTest1.cs”文件,然后选择“重命名”。 将文件名更改为“WatchFunctionUnitTests.cs”。 在出现的消息框中,选择“是”,将“UnitTest1”的所有引用重命名为“WatchFunctionUnitTests”。
在“解决方案资源管理器”窗口中,在“WatchFunctionsTests”项目下,单击“依赖项”,然后选择“添加项目引用”。
在“引用管理器”窗口中,选择“WatchPortalFunction”项目,然后选择“确定”。
为 WatchInfo 函数添加单元测试
现可将单元测试添加到测试项目。 在奢侈手表方案中,想要确保 WatchInfo 函数在请求的查询字符串中提供模型时始终返回正确响应,并且如果查询字符串为空或不包含 model
参数时返回错误响应。
将向 WatchFunctionsTests 添加一对 Fact 测试以验证此行为。
在“解决方案资源管理器”窗口中,双击“WatchFunctionUnitTests.cs”文件以在代码窗口中显示 WatchPortalFunction。
将以下
using
指令添加到文件顶部的列表中。using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Logging.Abstractions;
将 Test1 方法的名称更改为 TestWatchFunctionSuccess。
在 TestWatchFunctionSuccess 方法的正文中,添加以下代码。 此语句创建模拟 HTTP 上下文和 HTTP 请求。 该请求包括一个包含
model
参数的查询字符串,该参数设置为abc
。var queryStringValue = "abc"; var request = new DefaultHttpRequest(new DefaultHttpContext()) { Query = new QueryCollection ( new System.Collections.Generic.Dictionary<string, StringValues>() { { "model", queryStringValue } } ) };
在方法中添加以下语句。 该语句创建一个虚拟记录器。
var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
将以下代码添加到方法中。 这些语句调用 WatchInfo 函数,作为参数传入虚拟请求和记录器。
var response = WatchPortalFunction.WatchInfo.Run(request, logger); response.Wait();
将以下代码添加到方法中。 此代码检查函数的响应是否正确。 在此情况下,函数应返回包含预期正文数据的正确响应。
// Check that the response is an "OK" response Assert.IsAssignableFrom<OkObjectResult>(response.Result); // Check that the contents of the response are the expected contents var result = (OkObjectResult)response.Result; dynamic watchinfo = new { Manufacturer = "abc", CaseType = "Solid", Bezel = "Titanium", Dial = "Roman", CaseFinish = "Silver", Jewels = 15 }; string watchInfo = $"Watch Details: {watchinfo.Manufacturer}, {watchinfo.CaseType}, {watchinfo.Bezel}, {watchinfo.Dial}, {watchinfo.CaseFinish}, {watchinfo.Jewels}"; Assert.Equal(watchInfo, result.Value);
完整的方法应如下所示。
[Fact] public void TestWatchFunctionSuccess() { var queryStringValue = "abc"; var request = new DefaultHttpRequest(new DefaultHttpContext()) { Query = new QueryCollection ( new System.Collections.Generic.Dictionary<string, StringValues>() { { "model", queryStringValue } } ) }; var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger"); var response = WatchPortalFunction.WatchInfo.Run(request, logger); response.Wait(); // Check that the response is an "OK" response Assert.IsAssignableFrom<OkObjectResult>(response.Result); // Check that the contents of the response are the expected contents var result = (OkObjectResult)response.Result; dynamic watchinfo = new { Manufacturer = "abc", CaseType = "Solid", Bezel = "Titanium", Dial = "Roman", CaseFinish = "Silver", Jewels = 15 }; string watchInfo = $"Watch Details: {watchinfo.Manufacturer}, {watchinfo.CaseType}, {watchinfo.Bezel}, {watchinfo.Dial}, {watchinfo.CaseFinish}, {watchinfo.Jewels}"; Assert.Equal(watchInfo, result.Value); }
再添加两个名为 TestWatchFunctionFailureNoQueryString 和 TestWatchFunctionFailureNoModel 的方法。 TestWatchFunctionFailureNoQueryString 验证如果没有为 WatchInfo 函数提供查询字符串,则会失败。 如果函数传递的查询字符串不包含模型参数,则 TestWatchFunctionFailureNoModel 检查相同的失败。
[Fact] public void TestWatchFunctionFailureNoQueryString() { var request = new DefaultHttpRequest(new DefaultHttpContext()); var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger"); var response = WatchPortalFunction.WatchInfo.Run(request, logger); response.Wait(); // Check that the response is an "Bad" response Assert.IsAssignableFrom<BadRequestObjectResult>(response.Result); // Check that the contents of the response are the expected contents var result = (BadRequestObjectResult)response.Result; Assert.Equal("Please provide a watch model in the query string", result.Value); } [Fact] public void TestWatchFunctionFailureNoModel() { var queryStringValue = "abc"; var request = new DefaultHttpRequest(new DefaultHttpContext()) { Query = new QueryCollection ( new System.Collections.Generic.Dictionary<string, StringValues>() { { "not-model", queryStringValue } } ) }; var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger"); var response = WatchPortalFunction.WatchInfo.Run(request, logger); response.Wait(); // Check that the response is an "Bad" response Assert.IsAssignableFrom<BadRequestObjectResult>(response.Result); // Check that the contents of the response are the expected contents var result = (BadRequestObjectResult)response.Result; Assert.Equal("Please provide a watch model in the query string", result.Value); }
运行测试
在顶部菜单栏的“测试”下,选择“运行所有测试”。
在“测试资源管理器”窗口中,应成功完成所有三项测试。
在“解决方案资源管理器”窗口中,在“WatchPortalFunction”项目下,双击“WatchInfo.cs”以在代码编辑器中显示文件。
找到以下代码。
// Retrieve the model id from the query string string model = req.Query["model"];
更改设置
model
变量的语句,如下所示。 此更改模拟了开发者在代码中犯错。string model = req.Query["modelll"];
在顶部菜单栏的“测试”下,选择“运行所有测试”。 这一次,TestWatchFunctionSuccess 测试会失败。 发生此故障是因为 WatchInfo 函数未在查询字符串中找到名为
modelll
的参数,因此该函数返回错误响应。
在本单元中,你已了解了如何创建单元测试项目,以及如何为 Azure Functions 实现单元测试。