在 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 functions.

接下來的內容分為兩個不同小節,以不同的語言和環境為目標。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. 建立新的 Functions 應用程式,並將它命名為 FunctionsCreate a new Functions app and name it Functions
  2. 從範本建立 HTTP 函式 ,並將它命名為 MyHttpTriggerCreate an HTTP function from the template and name it MyHttpTrigger.
  3. 從範本建立計時器 函式,並將它命名為 MyTimerTriggerCreate a timer function from the template and name it MyTimerTrigger.
  4. 在方案中建立 XUnit 測試應用程式,並將其命名為「測試」。Create an xUnit Test app in the solution and name it Functions.Tests.
  5. 使用 NuGet 將參考從測試應用程式新增至 AspNetCoreUse NuGet to add a reference from the test app to Microsoft.AspNetCore.Mvc
  6. 從函式參考 函數 應用 程式。 測試 應用程式。Reference the Functions app from Functions.Tests app.

建立測試類別Create test classes

現在已建立專案,您可以建立用來執行自動化測試的類別。Now that the projects 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 其包含要在測試期間評估的內部訊息清單。You'll create a new class named ListLogger which holds an internal list of messages to evaluate during a testing. 若要執行必要的 ILogger 介面,類別需要範圍。To implement the required ILogger interface, the class needs a scope. 下列類別會模擬要傳遞至類別之測試案例的範圍 ListLoggerThe following class mocks a scope for the test cases to pass to the ListLogger class.

在函式中建立新的類別 。測試 名為 NullScope.cs 的專案,並輸入下列程式碼:Create a new class in Functions.Tests project named 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() { }
    }
}

接下來,在函式中建立新的類別 。測試 名為 ListLogger.cs 的專案,並輸入下列程式碼:Next, create a new class in Functions.Tests project named 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:提供的預設值 falseIsEnabled: 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.

接下來,在函式中建立新檔案 。測試 名為 LoggerTypes.cs 的專案,然後輸入下列程式碼:Next, create a new file in Functions.Tests project named 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.

現在在函式中建立新的類別 。測試 名為 TestFactory.cs 的專案,然後輸入下列程式碼:Now create a new class in Functions.Tests project named 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 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 類別實作下列成員: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.

最後,在函式中建立新的類別 。測試 名為 FunctionsTests.cs 的專案,然後輸入下列程式碼:Finally, create a new class in Functions.Tests project named 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 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);
            MyTimerTrigger.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.

如果您想要存取測試中的應用程式設定,您可以使用 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,使用下列命令取代現有測試命令: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. NCRONTAB 運算式等計時器設定並不是必要的,因為測試控管只是直接呼叫函式來測試結果。Timer configurations like NCRONTAB expressions are not required here as the test harness is simply calling the function directly to test the outcome.

接下來,使用 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",
  "disableOptimisticBPs": true,
  "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: