您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

在 Azure Functions 中测试代码的策略Strategies for testing your code in Azure Functions

本文演示如何为 Azure Functions 创建自动测试。This article demonstrates how to create automated tests for Azure Functions.

建议测试所有代码,但是,包装函数的逻辑并在函数外部创建测试可以获得最佳效果。Testing all code is recommended, however you may get the best results by wrapping up a Function's logic and creating tests outside the Function. 将逻辑抽象出来可以限制函数的代码行,并让该函数专门负责调用其他类或模块。Abstracting logic away limits a Function's lines of code and allows the Function to be solely responsible for calling other classes or modules. 但是,本文演示如何针对 HTTP 和计时器触发的函数创建自动测试。This article, however, demonstrates how to create automated tests against an HTTP and timer-triggered function.

下面的内容根据不同的语言和环境划分为两个不同的部分。The content that follows is split into two different sections meant to target different languages and environments. 可以了解:You can learn to build tests in:

GitHub 上提供了示例存储库。The sample repository is available on GitHub.

Visual Studio 中的 C#C# in Visual Studio

以下示例描述如何在 Visual Studio 中创建 C# 函数应用,并使用 xUnit 运行和测试该应用。The following example describes how to create a C# Function app in Visual Studio and run and tests with xUnit.

使用 Visual Studio 中的 C# 测试 Azure Functions

设置Setup

若要设置环境,请创建一个函数和测试应用。To set up your environment, create a Function and test app. 以下步骤可帮助你创建用于支持测试的应用和函数:The following steps help you create the apps and functions required to support the tests:

  1. 创建新函数应用并将其命名为 FunctionsCreate a new Functions app and name it Functions
  2. 从模板创建 HTTP 函数并将其命名为 HttpTriggerCreate an HTTP function from the template and name it HttpTrigger.
  3. 从模板创建计时器函数并将其命名为 TimerTriggerCreate a timer function from the template and name it TimerTrigger.
  4. 单击“文件”>“新建”>“项目”>“Visual C#”>“.NET Core”>“xUnit 测试项目”,在 Visual Studio 中创建 xUnit 测试应用,并将其命名为“Functions.Test” 。Create an xUnit Test app in Visual Studio by clicking File > New > Project > Visual C# > .NET Core > xUnit Test Project and name it Functions.Test.
  5. 使用 Nuget 从测试应用 Microsoft.AspNetCore.Mvc 添加引用Use Nuget to add a references from the test app Microsoft.AspNetCore.Mvc
  6. Functions.Test 应用引用 Functions 应用Reference the Functions app from Functions.Test app.

创建测试类Create test classes

创建应用程序后,可以创建用于运行自动测试的类。Now that the applications are created, you can create the classes used to run the automated tests.

每个函数采用 ILogger 的实例来处理消息日志记录。Each function takes an instance of ILogger to handle message logging. 有些测试不记录消息,或者与日志记录的实现方式无关。Some tests either don't log messages or have no concern for how logging is implemented. 还有一些测试需要评估记录的消息,以确定是否通过了测试。Other tests need to evaluate messages logged to determine whether a test is passing.

ListLogger 类用于实现 ILogger 接口并保存在消息的内部列表中,以便在测试期间用于评估。The ListLogger class is meant to implement the ILogger interface and hold in internal list of messages for evaluation during a test.

右键单击“Functions.Test”应用程序并选择“添加”>“类”,将类命名为 NullScope.cs,然后输入以下代码: Right-click on the Functions.Test application and select Add > Class, name it NullScope.cs and enter the following code:

using System;

namespace Functions.Tests
{
    public class NullScope : IDisposable
    {
        public static NullScope Instance { get; } = new NullScope();

        private NullScope() { }

        public void Dispose() { }
    }
}

接下来,右键单击“Functions.Test”应用程序并选择“添加”>“类”,将类命名为 ListLogger.cs,然后输入以下代码: Next, right-click on the Functions.Test application and select Add > Class, name it ListLogger.cs and enter the following code:

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 接口收缩的以下成员:The ListLogger class implements the following members as contracted by the ILogger interface:

  • BeginScope:范围将上下文添加到日志记录。BeginScope: Scopes add context to your logging. 在本例中,测试只是指向 NullScope 类中的静态实例,使测试能够正常运行。In this case, the test just points to the static instance on the NullScope class to allow the test to function.

  • IsEnabled:提供 false 的默认值。IsEnabled: A default value of false is provided.

  • Log:此方法使用提供的 formatter 函数来设置消息格式,然后将生成的文本添加到 Logs 集合。Log: This method uses the provided formatter function to format the message and then adds the resulting text to the Logs collection.

Logs 集合是 List<string> 的实例,在构造函数中初始化。The Logs collection is an instance of List<string> and is initialized in the constructor.

接下来,右键单击“Functions.Test”应用程序并选择“添加”>“类”,将类命名为 LoggerTypes.cs,然后输入以下代码: Next, right-click on the Functions.Test application and select Add > Class, name it LoggerTypes.cs and enter the following code:

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

此枚举指定测试使用的记录器类型。This enumeration specifies the type of logger used by the tests.

接下来,右键单击“Functions.Test”应用程序并选择“添加”>“类”,将类命名为 TestFactory.cs,然后输入以下代码: Next, right-click on the Functions.Test application and select Add > Class, name it TestFactory.cs and enter the following code:

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 DefaultHttpRequest CreateHttpRequest(string queryStringKey, string queryStringValue)
        {
            var request = new DefaultHttpRequest(new DefaultHttpContext())
            {
                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 类实现以下成员:The TestFactory class implements the following members:

  • Data:此属性返回示例数据的 IEnumerable 集合。Data: This property returns an IEnumerable collection of sample data. 键/值对表示传入查询字符串中的值。The key value pairs represent values that are passed into a query string.

  • CreateDictionary:此方法接受键/值对作为参数,并返回新的 Dictionary 用于创建 QueryCollection 来表示查询字符串值。CreateDictionary: This method accepts a key/value pair as arguments and returns a new Dictionary used to create QueryCollection to represent query string values.

  • CreateHttpRequest:此方法创建使用给定查询字符串参数初始化的 HTTP 请求。CreateHttpRequest: This method creates an HTTP request initialized with the given query string parameters.

  • CreateLogger:此方法基于记录器类型返回用于测试的记录器类。CreateLogger: Based on the logger type, this method returns a logger class used for testing. ListLogger 跟踪可在测试中评估的记录消息。The ListLogger keeps track of logged messages available for evaluation in tests.

接下来,右键单击“Functions.Test”应用程序并选择“添加”>“类”,将类命名为 FunctionsTests.cs,然后输入以下代码: Next, right-click on the Functions.Test application and select Add > Class, name it FunctionsTests.cs and enter the following code:

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 HttpFunction.Run(request, logger);
            Assert.Equal("Hello, Bill", 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 HttpFunction.Run(request, logger);
            Assert.Equal($"Hello, {queryStringValue}", response.Value);
        }

        [Fact]
        public void Timer_should_log_message()
        {
            var logger = (ListLogger)TestFactory.CreateLogger(LoggerTypes.List);
            TimerTrigger.Run(null, logger);
            var msg = logger.Logs[0];
            Assert.Contains("C# Timer trigger function executed at", msg);
        }
    }
}

在此类中实现的成员包括:The members implemented in this class are:

  • Http_trigger_should_return_known_string:此测试创建对 HTTP 函数发出的、其查询字符串值为 name=Bill 的请求,并检查是否返回了预期的响应。Http_trigger_should_return_known_string: This test creates a request with the query string values of name=Bill to an HTTP function and checks that the expected response is returned.

  • Http_trigger_should_return_string_from_member_data:此测试使用 xUnit 属性为 HTTP 函数提供示例数据。Http_trigger_should_return_string_from_member_data: This test uses xUnit attributes to provide sample data to the HTTP function.

  • Timer_should_log_message:此测试创建 ListLogger 的实例并将其传递给计时器函数。Timer_should_log_message: This test creates an instance of ListLogger and passes it to a timer functions. 运行该函数后,将检查日志以确保存在预期的消息。Once the function is run, then the log is checked to ensure the expected message is present.

如果要在测试中访问应用程序设置,可以使用 System.Environment.GetEnvironmentVariableIf you want to access application settings in your tests, you can use System.Environment.GetEnvironmentVariable.

运行测试Run tests

若要运行测试,请导航到“测试资源管理器”并单击“全部运行”。 To run the tests, navigate to the Test Explorer and click Run all.

使用 Visual Studio 中的 C# 测试 Azure Functions

调试测试Debug tests

若要调试测试,请在测试中设置一个断点,导航到“测试资源管理器”,然后单击“运行”>“调试上次运行”。 To debug the tests, set a breakpoint on a test, navigate to the Test Explorer and click Run > Debug Last Run.

VS Code 中的 JavaScriptJavaScript in VS Code

以下示例描述如何在 VS Code 中创建 JavaScript 函数应用,并使用 Jest 运行和测试该应用。The following example describes how to create a JavaScript Function app in VS Code and run and tests with Jest. 此过程使用 VS Code Functions 扩展来创建 Azure Functions。This procedure uses the VS Code Functions extension to create Azure Functions.

使用 VS Code 中的 JavaScript 测试 Azure Functions

设置Setup

若要设置环境,请运行 npm init,在空文件夹中初始化新的 Node.js 应用。To set up your environment, initialize a new Node.js app in an empty folder by running npm init.

npm init -y

接下来,运行以下命令安装 Jest:Next, install Jest by running the following command:

npm i jest

现在,请更新 package.json 以将现有的 test 命令替换为以下命令:Now update package.json to replace the existing test command with the following command:

"scripts": {
    "test": "jest"
}

创建测试模块Create test modules

初始化项目后,可以创建用于运行自动测试的模块。With the project initialized, you can create the modules used to run the automated tests. 首先,创建名为 testing 的新文件夹用于保存支持模块。Begin by creating a new folder named testing to hold the support modules.

testing 文件夹中添加一个新文件,将其命名为 defaultContext.js,然后添加以下代码:In the testing folder add a new file, name it defaultContext.js, and add the following code:

module.exports = {
    log: jest.fn()
};

此模块模拟 log 函数来表示默认的执行上下文。This module mocks the log function to represent the default execution context.

接下来,添加一个新文件,将其命名为 defaultTimer.js,然后添加以下代码:Next, add a new file, name it defaultTimer.js, and add the following code:

module.exports = {
    IsPastDue: false
};

此模块实现 IsPastDue 属性,表示该实例是一个虚构的计时器实例。This module implements the IsPastDue property to stand is as a fake timer instance.

接下来,使用 VS Code Functions 扩展创建新的 JavaScript HTTP 函数,并将其命名为 HttpTriggerNext, use the VS Code Functions extension to create a new JavaScript HTTP Function and name it HttpTrigger. 创建函数后,在名为 index.test.js 的同一文件夹中添加一个新文件,然后添加以下代码:Once the function is created, add a new file in the same folder named index.test.js, and add the following code:

const httpFunction = require('./index');
const context = require('../testing/defaultContext')

test('Http trigger should return known text', async () => {

    const request = {
        query: { name: 'Bill' }
    };

    await httpFunction(context, request);

    expect(context.log.mock.calls.length).toBe(1);
    expect(context.res.body).toEqual('Hello Bill');
});

模板中的 HTTP 函数返回与查询字符串中提供的名称连接在一起的“Hello”字符串。The HTTP function from the template returns a string of "Hello" concatenated with the name provided in the query string. 此测试创建虚构的请求实例,并将其传递给 HTTP 函数。This test creates a fake instance of a request and passes it to the HTTP function. 此测试检查 log 方法是否调用了一次,并且返回的文本是否等于“Hello Bill”。The test checks that the log method is called once and the returned text equals "Hello Bill".

接下来,使用 VS Code Functions 扩展创建新的 JavaScript 计时器函数,并将其命名为 TimerTriggerNext, use the VS Code Functions extension to create a new JavaScript Timer Function and name it TimerTrigger. 创建函数后,在名为 index.test.js 的同一文件夹中添加一个新文件,然后添加以下代码:Once the function is created, add a new file in the same folder named index.test.js, and add the following code:

const timerFunction = require('./index');
const context = require('../testing/defaultContext');
const timer = require('../testing/defaultTimer');

test('Timer trigger should log message', () => {
    timerFunction(context, timer);
    expect(context.log.mock.calls.length).toBe(1);
});

模板中的计时器函数在函数正文的末尾记录一条消息。The timer function from the template logs a message at the end of the body of the function. 此测试确保调用 log 函数一次。This test ensures the log function is called once.

运行测试Run tests

若要运行测试,请按 CTRL + ~ 打开命令窗口,然后运行 npm testTo run the tests, press CTRL + ~ to open the command window, and run npm test:

npm test

使用 VS Code 中的 JavaScript 测试 Azure Functions

调试测试Debug tests

若要调试测试,请将以下配置添加到 launch.json 文件中:To debug your tests, add the following configuration to your launch.json file:

{
  "type": "node",
  "request": "launch",
  "name": "Jest Tests",
  "program": "${workspaceRoot}\\node_modules\\jest\\bin\\jest.js",
  "args": [
      "-i"
  ],
  "internalConsoleOptions": "openOnSessionStart"
}

接下来,在测试中设置一个断点并按 F5Next, set a breakpoint in your test and press F5.

后续步骤Next steps

了解如何为函数编写自动测试后,请继续参阅以下资源:Now that you've learned how to write automated tests for your functions, continue with these resources: