在 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. 建立新的 Functions 應用程式,並將它命名為 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 Studio 中按一下 [檔案] > [新增] > [專案] > [Visual C#] > [.NET Core] > [xUnit 測試專案] 來建立 xUnit 測試應用程式,並將它命名為 Functions.TestCreate 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 新增來自AspNetCore測試應用程式的參考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.

以滑鼠右鍵按一下[函數] 和 [測試] 應用程式,然後選取 [新增 > 類別],將其命名為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() { }
    }
}

接下來,以滑鼠右鍵按一下[函式] 和 [測試應用程式],然後選取 [新增 > 類別],將其命名為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. 在此情況下,測試只會指向 @no__t 0 類別上的靜態實例,讓測試能夠運作。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.

接下來,以滑鼠右鍵按一下 Functions.Test 應用程式,並選取 [新增] > [類別],將它命名為 ListTypes.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.

如果您想要存取測試中的應用程式設定,您可以使用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.

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