你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

使用 Visual Studio 开发 Azure Functions

使用 Visual Studio 可以开发、测试 C# 类库函数并将其部署到 Azure。 如果这是你第一次体验 Azure Functions,请参阅 Azure Functions 简介

若要立即开始,请考虑完成 Visual Studio 的函数快速入门

本文详细介绍如何使用 Visual Studio 开发 C# 类库函数并将其发布到 Azure。 有两个模型用于开发 C# 类库函数:独立辅助角色模型进程内模型

你正在阅读本文中的独立辅助角色模型版本。 你可以在文章顶部选择首选模型。

你正在阅读本文中的进程内模型版本。 你可以在文章顶部选择首选模型。

除非另有说明,否则将演示 Visual Studio 2022 的过程和示例。 有关 Visual Studio 2022 版本的详细信息,请参阅发行说明预览发行说明

先决条件

  • Visual Studio 2022,包括“Azure 开发”工作负载。

  • 所需的其他资源(例如 Azure 存储帐户)将在发布过程中在订阅中创建。

  • 如果没有 Azure 订阅,请在开始之前创建一个 Azure 免费帐户

创建 Azure Functions 项目

Visual Studio 中的 Azure Functions 项目模板创建了一个 C# 类库项目,该项目可发布到 Azure 中的函数应用。 可使用函数应用将函数分组为逻辑单元,以便更轻松地管理、部署、缩放和共享资源。

  1. 在 Visual Studio 菜单中,选择“文件”>“新建”>“项目”。

  2. 在“创建新项目”中,在搜索框中输入“functions”,选择“Azure Functions”模板,然后选择“下一步”

  3. 在“配置新项目”中,输入项目的“项目名称”,然后选择“创建”。 函数应用名称必须可以充当 C# 命名空间,因此请勿使用下划线、连字符或任何其他的非字母数字字符。

  4. 对于“创建新的 Azure Functions 应用程序”设置,请使用下表中的值:

    设置 说明
    .NET 版本 .NET 6(独立) 此值创建在隔离工作进程中运行的函数项目。 独立工作进程支持其他非 LTS 版本的 .NET 以及 .NET Framework。 有关详细信息,请参阅 Azure Functions 运行时版本概述
    函数模板 HTTP 触发器 此值会创建由 HTTP 请求触发的函数。
    存储帐户(AzureWebJobsStorage) 存储模拟器 由于 Azure 中的函数应用需要存储帐户,因此在将项目发布到 Azure 时会分配或创建一个存储帐户。 HTTP 触发器不使用 Azure 存储帐户连接字符串;所有其他触发器类型需要有效的 Azure 存储帐户连接字符串。
    授权级别 匿名 在未提供密钥的情况下,任何客户端都可以触发创建的函数。 通过此授权设置可以轻松测试新函数。 有关密钥和授权的详细信息,请参阅授权密钥HTTP 和 Webhook 绑定

    Screenshot of Azure Functions project settings

    设置 说明
    .NET 版本 .NET 6 此值会创建一个函数项目,该项目在进程内使用 Azure Functions 运行时版本 4.x 运行。 有关详细信息,请参阅 Azure Functions 运行时版本概述
    函数模板 HTTP 触发器 此值会创建由 HTTP 请求触发的函数。
    存储帐户(AzureWebJobsStorage) 存储模拟器 由于 Azure 中的函数应用需要存储帐户,因此在将项目发布到 Azure 时会分配或创建一个存储帐户。 HTTP 触发器不使用 Azure 存储帐户连接字符串;所有其他触发器类型需要有效的 Azure 存储帐户连接字符串。
    授权级别 匿名 在未提供密钥的情况下,任何客户端都可以触发创建的函数。 通过此授权设置可以轻松测试新函数。 有关密钥和授权的详细信息,请参阅授权密钥HTTP 和 Webhook 绑定

    Screenshot of Azure Functions project settings

    请确保将“授权级别”设置为“匿名”。 如果选择默认级别的函数,需要在请求中提供函数密钥才能访问函数终结点。

  5. 选择“创建”以创建函数项目和 HTTP 触发器函数。

创建 Azure Functions 项目后,项目模板会创建 C# 项目,安装 Microsoft.Azure.Functions.WorkerMicrosoft.Azure.Functions.Worker.Sdk NuGet 包,并设置目标框架。

创建 Azure Functions 项目后,项目模板会创建 C# 项目,安装 Microsoft.NET.Sdk.Functions NuGet 包,并设置目标框架。

新项目包含以下文件:

  • host.json:用于配置 Functions 主机。 在本地和 Azure 中运行时,都会应用这些设置。 有关详细信息,请参阅 host.json 参考

  • local.settings.json:维护本地运行函数时使用的设置。 在 Azure 中运行时不使用这些设置。 有关详细信息,请参阅本地设置文件

    重要

    由于 local.settings.json 文件可能包含机密,因此必须将其从项目源代码管理中排除。 请确保此文件的“复制到输出目录”设置已设置为“如果较新则复制”。

有关详细信息,请参阅独立辅助角色指南中的项目结构

有关详细信息,请参阅 Functions 类库项目

在本地使用应用设置

在 Azure 中的函数应用中运行时,函数所需的设置安全地存储在应用设置中。 在本地开发期间,这些设置将改为添加到 local.settings.json 文件中的 Values 集合。 local.settings.json 文件还会存储本地开发工具使用的设置。

你的项目的 local.settings.json 文件中的 Values 集合中的项旨在镜像 Azure 中你的函数应用的应用程序设置中的项。

当你发布项目时,Visual Studio 不会自动上传 local.settings.json 中的设置。 为了确保这些设置也存在于 Azure 的函数应用中,请在发布项目之后上传它们。 有关详细信息,请参阅函数应用设置。 永远不会发布 ConnectionStrings 集合中的值。

还可以在代码中将函数应用设置值读取为环境变量。 有关详细信息,请参阅环境变量

为本地开发配置项目

Functions 运行时在内部使用 Azure 存储帐户。 对于除 HTTP 和 Webhook 之外的所有触发器类型,请将 Values.AzureWebJobsStorage 键设置为有效的 Azure 存储帐户连接字符串。 函数应用还可以在项目所需的 AzureWebJobsStorage 连接设置中使用 Azure 模拟器。 若要使用模拟器,请将 AzureWebJobsStorage 的值设置为 UseDevelopmentStorage=true。 在部署之前,请将此设置更改为实际的存储帐户连接字符串。 有关详细信息,请参阅本地存储模拟器

若要设置存储帐户连接字符串,请执行以下操作:

  1. 在 Azure 门户中导航到存储帐户。

  2. 在“访问密钥”选项卡的“安全性 + 网络”下面,复制 key1 的连接字符串。

  3. 在你的项目中,打开 local.settings.json 文件,并将 AzureWebJobsStorage 键的值设置为你复制的连接字符串。

  4. 重复上一步骤,将唯一键添加到函数所需的其他任何连接的 Values 数组。

将函数添加到项目

在 C# 类库函数中,可以通过在代码中应用属性来定义函数使用的绑定。 从提供的模板创建函数触发器时,将为你应用触发器属性。

  1. 在“解决方案资源管理器”中,右键单击你的项目节点,然后选择“添加”>“新建 Azure 函数”。

  2. 输入类的名称,然后选择“添加”。

  3. 选择你的触发器,设置所需的绑定属性,然后选择“添加”。 以下示例显示了用于创建队列存储触发器函数的设置。

    Create a Queue storage trigger function

    对于 Azure 存储服务触发器,请选中“配置连接”框,系统会提示你选择使用 Azurite 存储模拟器或引用预配的 Azure 存储帐户。 选择“下一步”,如果选择存储帐户,Visual Studio 会尝试连接到 Azure 帐户并获取连接字符串。 选择“将连接字符串值保存在本地用户机密文件中”,然后选择“完成”来创建触发器类。

    此触发器示例使用应用程序设置与名为 QueueStorage 的密钥建立存储连接。 此密钥存储在 local.settings.json 文件中,引用 Azure 模拟器或 Azure 存储帐户。

  4. 检查新添加的类。 例如,以下 C# 类表示一个基本的队列存储触发器函数:

    你会看到一个属性为 Function 的静态 Run() 方法。 该属性指示该方法是函数的入口点。

    using System;
    using Azure.Storage.Queues.Models;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Extensions.Logging;
    
    namespace Company.Function
    {
        public class QueueTriggerCSharp
        {
            private readonly ILogger<QueueTriggerCSharp> _logger;
    
            public QueueTriggerCSharp(ILogger<QueueTriggerCSharp> logger)
            {
                _logger = logger;
            }
    
            [Function(nameof(QueueTriggerCSharp))]
            public void Run([QueueTrigger("PathValue", Connection = "ConnectionValue")] QueueMessage message)
            {
                _logger.LogInformation($"C# Queue trigger function processed: {message.MessageText}");
            }
        }
    }
    

    你会看到一个属性为 FunctionName 的静态 Run() 方法。 该属性指示该方法是函数的入口点。

    using System;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Host;
    using Microsoft.Extensions.Logging;
    
    namespace Company.Function
    {
        public class QueueTriggerCSharp
        {
            [FunctionName("QueueTriggerCSharp")]
            public void Run([QueueTrigger("PathValue", Connection = "ConnectionValue")]string myQueueItem, ILogger log)
            {
                log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
            }
        }
    }
    

已向提供给入口点方法的每个绑定参数提供了特定于绑定的属性。 该属性采用绑定信息作为参数。 在上例中,第一个参数应用了 QueueTrigger 属性,它表示一个队列存储触发器函数。 队列名称和连接字符串设置名称作为参数传递给 QueueTrigger 属性。 有关详细信息,请参阅 Azure Functions 的 Azure 队列存储绑定

可以使用上述过程向函数应用项目添加更多的函数。 项目中的每个函数可以有不同的触发器,但每个函数的触发器必须刚好一个。 有关详细信息,请参阅 Azure Functions 触发器和绑定概念

添加绑定

使用触发器时,输入和输出绑定是作为绑定属性添加到函数的。 向函数添加绑定,如下所示:

  1. 确保已为本地开发配置项目

  2. 通过在绑定的参考文章中找到特定于绑定的 NuGet 包要求,为特定绑定添加相应的 NuGet 扩展包。 例如,可以在事件中心绑定参考文章中找到事件中心触发器的包要求。

  3. 在包管理器控制台中使用以下命令安装特定包:

    Install-Package Microsoft.Azure.Functions.Worker.Extensions.<BINDING_TYPE> -Version <TARGET_VERSION>
    
    Install-Package Microsoft.Azure.WebJobs.Extensions.<BINDING_TYPE> -Version <TARGET_VERSION>
    

    在此示例中,请将 <BINDING_TYPE> 替换为特定于绑定扩展的名称,将 <TARGET_VERSION> 替换为包的特定版本,例如 4.0.0。 在 NuGet.org 上的单个包页上列出了有效版本。

  4. 如果有绑定所需的应用设置,请将其添加到本地设置文件中的 Values 集合。

    函数在本地运行时将使用这些值。 当函数在 Azure 的函数应用中运行时,它将使用函数应用设置。 使用 Visual Studio 可以轻松地将本地设置发布到 Azure

  5. 将适当的绑定属性添加到方法签名。 在以下示例中,一条队列消息触发了该函数,而输出绑定则创建了一条新的队列消息,在不同的队列中使用了相同的文本。

     public class QueueTrigger
    {
        private readonly ILogger _logger;
    
        public QueueTrigger(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<QueueTrigger>();
        }
    
        [Function("CopyQueueMessage")]
        [QueueOutput("myqueue-items-destination", Connection = "QueueStorage")]
        public string Run([QueueTrigger("myqueue-items-source", Connection = "QueueStorage")] string myQueueItem)
        {
            _logger.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
            return myQueueItem;
        }
    }
    

    QueueOutput 属性定义了方法上的绑定。 对于多个输出绑定,请改为将此属性放置在返回对象的字符串属性上。 有关详细信息,请参阅多个输出绑定

    public static class SimpleExampleWithOutput
    {
        [FunctionName("CopyQueueMessage")]
        public static void Run(
            [QueueTrigger("myqueue-items-source", Connection = "QueueStorage")] string myQueueItem, 
            [Queue("myqueue-items-destination", Connection = "QueueStorage")] out string myQueueItemCopy,
            ILogger log)
        {
            log.LogInformation($"CopyQueueMessage function processed: {myQueueItem}");
            myQueueItemCopy = myQueueItem;
        }
    }
    

    out 参数上的 Queue 属性定义了输出绑定。

    到队列存储的连接从 QueueStorage 设置获取。 有关详细信息,请参阅特定绑定的参考文章。

有关 Functions 支持的绑定的完整列表,请参阅支持的绑定

在本地运行函数

Azure Functions Core Tools 允许在本地开发计算机上运行 Azure Functions 项目。 按 F5 调试 Functions 项目时,本地 Functions 主机 (func.exe) 会开始侦听本地端口(通常为 7071)。 任何可调用的函数终结点都将写入输出,你可以使用这些终结点来测试函数。 有关详细信息,请参阅使用 Azure Functions Core Tools。 当你首次从 Visual Studio 启动某个函数时,系统会提示你安装这些工具。

若要在调试模式下于 Visual Studio 中启动函数:

  1. 按 F5。 如果系统提示,请按 Visual Studio 的请求下载和安装 Azure Functions Core (CLI) 工具。 你还需要启用防火墙例外,这样工具才能处理 HTTP 请求。

  2. 当项目正在运行时,请像测试已部署函数一样测试代码。

    在调试模式下运行 Visual Studio 时,将会按预期命中断点。

有关使用 Visual Studio 进行测试的更详细方案,请参阅测试函数

发布到 Azure

将函数项目发布到 Azure 时,Visual Studio 使用 zip 部署来部署项目文件。 如果可能,还应选择 Run-From-Package,以便项目在部署 (.zip) 包中运行。 有关详细信息,请参阅从 Azure 中的包文件运行函数

不要使用 Web 部署 (msdeploy) 部署 Azure Functions。

使用以下步骤将项目发布到 Azure 中的函数应用。

  1. 在“解决方案资源管理器” 中,右键单击该项目并选择“发布”。 在“目标”中,选择“Azure”,然后选择“下一步”。

    Screenshot of publish window.

  2. 对于“特定目标”,请选择“Azure 函数应用(Windows)”(这将创建一个在 Windows 上运行的函数应用),然后选择“下一步”。

    Screenshot of publish window with specific target.

  3. 在“函数实例”中,选择“创建新的 Azure 函数…”。

    Screenshot of create a new function app instance.

  4. 使用下表中指定的值创建新的实例:

    设置 描述
    名称 全局唯一名称 用于唯一标识新 Function App 的名称。 接受此名称或输入新名称。 有效的字符是 a-z0-9-
    订阅 你的订阅 要使用的 Azure 订阅。 接受此订阅,或从下拉列表中选择一个新订阅。
    资源组 资源组的名称 你要在其中创建函数应用的资源组。 选择“新建”来创建一个新的资源组。 也可以从下拉列表选择现有的资源组。
    计划类型 消耗 将项目发布到在消耗计划中运行的函数应用时,只需为函数应用的执行付费。 其他托管计划会产生更高的成本。
    位置 应用服务的位置 在靠近你或者靠近函数访问的其他服务的区域中选择一个位置
    Azure 存储 常规用途存储帐户 Functions 运行时需要 Azure 存储帐户。 选择“新建”即可配置常规用途存储帐户。 也可选择一个符合存储帐户要求的现有帐户。
    Application Insights Application Insights 实例 应为函数应用启用 Application Insights 集成。 选择“新建”,在新的或现有的 Log Analytics 工作区中创建新实例。 也可以选择现有的实例。

    Screenshot of Create App Service dialog.

  5. 选择“创建”,在 Azure 中创建一个函数应用及其相关资源。 资源创建的状态将显示在窗口左下角。

  6. 在“函数实例”中,确保已选中“从包文件运行”。 启用从包运行模式,函数应用将使用 Zip 部署进行部署。 建议为你的函数项目使用 Zip 部署方法,因为它可提高性能。

    Screenshot of Finish profile creation.

  7. 选择“完成”,然后在“发布”页面上选择“发布”,将包含项目文件的包部署到 Azure 中的新函数应用 。

    部署完成后,Azure 中函数应用的根 URL 将显示在“发布”选项卡中。

  8. 在“发布”选项卡中的“托管”部分中,选择“在 Microsoft Azure 门户中打开”。 此操作会在 Microsoft Azure 门户中打开新的函数应用 Azure 资源。

    Screenshot of Publish success message.

函数应用设置

在你发布项目时,Visual Studio 不会自动上传这些设置。 还必须将添加到 local.settings.json 的任何设置添加到 Azure 函数应用中。

将所需设置上传到 Azure 中的函数应用的最简单方法是展开“承载”部分旁边的三个点,选择“管理 Azure 应用服务设置”链接(在成功发布项目后显示)。

Settings in Publish window

选择此链接将显示用于函数应用的“应用程序设置”对话框,可以在其中添加新应用程序设置或修改现有设置。

Application settings

“本地”会显示 local.settings.json 文件中的设置值,“远程”会显示 Azure 的函数应用中的当前设置值。 选择“添加设置”以创建新的应用设置。 使用“从本地插入值”链接将设置值复制到“远程”字段。 你选择“确定”后,挂起的更改将写入本地设置文件和函数应用。

注意

默认情况下,不会将 local.settings.json 文件签入到源代码管理中。 这意味着,如果你从源代码管理中克隆本地 Functions 项目,则项目没有 local.settings.json 文件。 在这种情况下,需要在项目根目录中手动创建 local.settings.json 文件,以便 Application Settings 对话框按预期工作。

还可以采用以下这些其他方法之一来管理应用程序设置:

Remote Debugging

要远程调试函数应用,必须发布项目的调试配置。 还需要在 Azure 中的函数应用中启用远程调试。

本部分假设你已使用发布配置发布到函数应用。

远程调试注意事项

  • 不建议对生产服务进行远程调试。
  • 如果已启用“仅我的代码”调试,请将其禁用。
  • 远程调试时避免长时间停止在断点处。 Azure 会将停止时间超过几分钟的进程视为无反应进程而将其关闭。
  • 进行调试的时候,服务器会向 Visual Studio 发送数据,这可能会影响到带宽费用。 有关带宽费率的信息,请参阅 Azure 定价
  • 在函数应用中,远程调试在 48 小时后自动禁用。 48 小时后,你需要重新启用远程调试。

附加调试器

附加调试器的方式取决于执行模式。 调试隔离工作进程应用时,当前需要将远程调试器附加到单独的 .NET 进程,并且还需要执行其他几个配置步骤。

完成后,应禁用远程调试

要将远程调试器附加到在独立于 Functions 主机的进程中运行的函数应用,请执行以下操作:

  1. 在“发布”选项卡中,选择“托管”部分中的省略号 (...),然后选择“下载发布配置文件”。 此操作将下载发布配置文件的副本并打开下载位置。 你需要此文件,其中包含用于附加到 Azure 中运行的隔离工作进程的凭据。

    注意

    .publishsettings 文件包含你的凭据(未编码),这些凭据用于管理你的函数应用。 确保此文件安全的最佳做法是,将其暂时存储在源目录的外部(例如存储在 Libraries\Documents 文件夹中),然后在不再需要该文件时将其删除。 获得了 .publishsettings 文件访问权的恶意用户可以编辑、创建和删除函数应用。

  2. 再次在“发布”选项卡中,选择“托管”部分中的省略号 (...),然后选择“附加调试器”。

    Screenshot of attaching the debugger from Visual Studio.

    Visual Studio 连接到函数应用并启用远程调试(如果尚未启用)。

    注意

    由于远程调试器无法连接到主机进程,因此可能会出现错误。 在任何情况下,默认调试都不会中断代码。

  3. 返回 Visual Studio,复制“发布”页中“托管”下的“站点”的 URL。

  4. 在“调试”菜单中选择“附加到进程”,然后在“附加到进程”窗口中将 URL 粘贴到“连接目标”中,删除 https:// 并追加端口 :4024

    验证目标是否类似于 <FUNCTION_APP>.azurewebsites.net:4024,然后按 Enter。

    Visual Studio attach to process dialog

  5. 如果系统提示,请允许通过本地防火墙访问 Visual Studio。

  6. 如果系统提示输入凭据而不是本地用户凭据选择,请选择其他帐户(通过 Windows 上的“更多选择”)。 在 Windows 上的身份验证对话框中,为“电子邮件地址”和“密码”提供已发布的配置文件中的 userName 和 userPWD 的值。 在与部署服务器建立安全连接后,将显示可用的进程。

    Visual Studio enter credential

  7. 选中“显示所有用户的进程”,然后依次选择“dotnet.exe”和“附加”。 操作完成后,会附加到在隔离工作进程中运行的 C# 类库代码。 此时,你可正常调试函数应用。

要将远程调试器附加到 Functions 主机进程内运行的函数应用,请执行以下操作:

  • 在“发布”选项卡中,选择“托管”部分中的省略号 (...),然后选择“附加调试器”。

    Screenshot of attaching the debugger from Visual Studio.

Visual Studio 连接到函数应用并启用远程调试(如果尚未启用)。 它还查找调试器并将其附加到应用的主机进程。 此时,你可正常调试函数应用。

禁用远程调试

远程调试完代码后,应在 Azure 门户中禁用远程调试。 远程调试将在 48 小时后自动禁用,以防你忘记。

  1. 在项目中的“发布”选项卡中,选择“托管”部分中的省略号 (...),然后选择“在 Azure 门户中打开”。 此操作将在项目部署到的 Azure 门户中打开函数应用。

  2. 在函数应用中,选择“设置”下的“配置”,选择“常规设置”,将“远程调试”设置为“关闭”,选择“保存”,然后选择“继续”。

函数应用重启后,你将不能再远程连接到远程进程。 可使用 Azure 门户中的同一选项卡在 Visual Studio 外部启用远程调试。

监视函数

监视函数执行的建议方法是将函数应用与 Azure Application Insights 集成。 在 Visual Studio 发布期间创建函数应用时,应启用此集成。

如果由于某种原因,在发布过程中未进行集成,仍应为 Azure 中的函数应用启用 Application Insights 集成

若要了解有关使用 Application Insights 进行监视的详细信息,请参阅监视 Azure Functions

测试函数

本部分介绍如何创建可使用 xUnit 进行测试的 C# 进程内模型项目。

Testing Azure Functions with C# in Visual Studio

1. 设置

按照以下步骤配置支持测试所需的环境(包括应用项目和函数):

  1. 创建新函数应用并将其命名为 Functions
  2. 从模板创建 HTTP 函数并将其命名为“MyHttpTrigger”。
  3. 从模板创建计时器函数并将其命名为“MyTimerTrigger”。
  4. 在解决方案中创建 xUnit 测试应用并将其命名为“Functions.Tests”。 删除默认测试文件。
  5. 使用 NuGet 从测试应用添加对 Microsoft.AspNetCore.Mvc 的引用
  6. 从 Functions.Tests 应用引用 Functions 应用

创建项目后,可以创建用于运行自动化测试的类。

2.创建测试类

每个函数采用 ILogger 的实例来处理消息日志记录。 有些测试不记录消息,或者与日志记录的实现方式无关。 还有一些测试需要评估记录的消息,以确定是否通过了测试。

  1. 创建一个名为 ListLogger 的类,其中包含要在测试过程中评估的消息的内部列表。 若要实现所需的 ILogger 接口,类需要范围。 下面的类模拟要传递给 ListLogger 类的测试用例的范围。

  2. 在 Functions.Tests 项目中创建一个名为 NullScope.cs 的新类,并添加以下代码:

    using System;
    
    namespace Functions.Tests
    {
        public class NullScope : IDisposable
        {
            public static NullScope Instance { get; } = new NullScope();
    
            private NullScope() { }
    
            public void Dispose() { }
        }
    }
    
  3. 在 Functions.Tests 项目中创建一个名为 ListLogger.cs 的类,并添加以下代码:

    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace Functions.Tests
    {
        public class ListLogger : ILogger
        {
            public IList<string> Logs;
    
            public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;
    
            public bool IsEnabled(LogLevel logLevel) => false;
    
            public ListLogger()
            {
                this.Logs = new List<string>();
            }
    
            public void Log<TState>(LogLevel logLevel,
                                    EventId eventId,
                                    TState state,
                                    Exception exception,
                                    Func<TState, Exception, string> formatter)
            {
                string message = formatter(state, exception);
                this.Logs.Add(message);
            }
        }
    }
    

    ListLogger 类实现 ILogger 接口收缩的以下成员:

    • BeginScope:范围将上下文添加到日志记录。 在本例中,测试只是指向 NullScope 类中的静态实例,使测试能够正常运行。

    • IsEnabled:提供 false 的默认值。

    • Log:此方法使用提供的 formatter 函数来设置消息格式,然后将生成的文本添加到 Logs 集合。

    Logs 集合是 List<string> 的实例,在构造函数中初始化。

  4. 在 Functions.Tests 项目中创建一个名为 LoggerTypes.cs 的代码文件,并添加以下代码:

    namespace Functions.Tests
    {
        public enum LoggerTypes
        {
            Null,
            List
        }
    }
    

    此枚举指定测试使用的记录器类型。

  5. 在 Functions.Tests 项目中创建一个名为 TestFactory.cs 的类,并添加以下代码:

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Http.Internal;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Logging.Abstractions;
    using Microsoft.Extensions.Primitives;
    using System.Collections.Generic;
    
    namespace Functions.Tests
    {
        public class TestFactory
        {
            public static IEnumerable<object[]> Data()
            {
                return new List<object[]>
                {
                    new object[] { "name", "Bill" },
                    new object[] { "name", "Paul" },
                    new object[] { "name", "Steve" }
    
                };
            }
    
            private static Dictionary<string, StringValues> CreateDictionary(string key, string value)
            {
                var qs = new Dictionary<string, StringValues>
                {
                    { key, value }
                };
                return qs;
            }
    
            public static HttpRequest CreateHttpRequest(string queryStringKey, string queryStringValue)
            {
                var context = new DefaultHttpContext();
                var request = context.Request;
                request.Query = new QueryCollection(CreateDictionary(queryStringKey, queryStringValue));
                return request;
            }
    
            public static ILogger CreateLogger(LoggerTypes type = LoggerTypes.Null)
            {
                ILogger logger;
    
                if (type == LoggerTypes.List)
                {
                    logger = new ListLogger();
                }
                else
                {
                    logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
                }
    
                return logger;
            }
        }
    }
    

    TestFactory 类实现以下成员:

    • Data:此属性返回示例数据的 IEnumerable 集合。 键/值对表示传入查询字符串中的值。

    • CreateDictionary:此方法接受键/值对作为参数,并返回新的 Dictionary 用于创建 QueryCollection 来表示查询字符串值。

    • CreateHttpRequest:此方法创建使用给定查询字符串参数初始化的 HTTP 请求。

    • CreateLogger:此方法基于记录器类型返回用于测试的记录器类。 ListLogger 跟踪可在测试中评估的记录消息。

  6. 在 Functions.Tests 项目中创建一个名为 FunctionsTests.cs 的类,并添加以下代码:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using Xunit;
    
    namespace Functions.Tests
    {
        public class FunctionsTests
        {
            private readonly ILogger logger = TestFactory.CreateLogger();
    
            [Fact]
            public async void Http_trigger_should_return_known_string()
            {
                var request = TestFactory.CreateHttpRequest("name", "Bill");
                var response = (OkObjectResult)await MyHttpTrigger.Run(request, logger);
                Assert.Equal("Hello, Bill. This HTTP triggered function executed successfully.", response.Value);
            }
    
            [Theory]
            [MemberData(nameof(TestFactory.Data), MemberType = typeof(TestFactory))]
            public async void Http_trigger_should_return_known_string_from_member_data(string queryStringKey, string queryStringValue)
            {
                var request = TestFactory.CreateHttpRequest(queryStringKey, queryStringValue);
                var response = (OkObjectResult)await MyHttpTrigger.Run(request, logger);
                Assert.Equal($"Hello, {queryStringValue}. This HTTP triggered function executed successfully.", response.Value);
            }
    
            [Fact]
            public void Timer_should_log_message()
            {
                var logger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
                new MyTimerTrigger().Run(null, logger);
                var msg = logger.Logs[0];
                Assert.Contains("C# Timer trigger function executed at", msg);
            }
        }
    }
    

    在此类中实现的成员包括:

    • Http_trigger_should_return_known_string:此测试创建对 HTTP 函数发出的、其查询字符串值为 name=Bill 的请求,并检查是否返回了预期的响应。

    • Http_trigger_should_return_string_from_member_data:此测试使用 xUnit 属性为 HTTP 函数提供示例数据。

    • Timer_should_log_message:此测试创建 ListLogger 的实例并将其传递给计时器函数。 运行该函数后,将检查日志以确保存在预期的消息。

  7. 若要在测试中访问应用程序设置,可以将包含模拟环境变量值的 IConfiguration 实例注入到函数中。

3.运行测试

若要运行测试,请导航到“测试资源管理器”,然后选择“在视图中运行所有测试”。

Testing Azure Functions with C# in Visual Studio

4.调试测试

若要调试测试,请在测试中设置一个断点,导航到“测试资源管理器”,然后选择“运行”>“调试上次运行”。

后续步骤

有关 Azure Functions Core Tools 的详细信息,请参阅使用 Azure Functions Core Tools