練習 - 對 Azure 函數進行單元測試

已完成

單元測試是一種敏捷式方法的基礎部分。 Visual Studio 提供測試專案範本。 請使用此範本為您的應用程式建立單元測試,您也可以套用相同的技巧來測試 Azure Functions。

在豪華鐘錶線上網站案例中,您的開發小組有一個原則,即在單元測試中達到至少 80% 的程式碼涵蓋範圍。 您想要為 Azure Functions 實作相同的原則。

在這裡,您會了解如何使用 xUnit 測試架構搭配 Visual Studio 來測試 Azure Functions。

建立單元測試專案

第一個步驟是建立包含單元測試的專案,並將其新增至保存 Azure Functions 應用程式的解決方案。 請使用下列步驟來建立單元測試專案,以測試 WatchInfo 函式。

  1. 在 Visual Studio 的 [方案總管] 視窗中,以滑鼠右鍵按一下 WatchPortalFunction 方案,選取 [新增],然後選取 [新增專案]

    [方案總管] 的螢幕擷取畫面,其中顯示 [新增專案至解決方案] 命令。

  2. 在 [新增專案] 視窗中,向下捲動並選取 [xUnit 測試專案] C#+ 圖示範本,然後選取 [下一步]

    [新增專案] 視窗的螢幕擷取畫面。已選取 xUnit 測試專案範本。

  3. [設定新專案] 視窗隨即出現。 在 [專案名稱] 欄位中,輸入 WatchFunctionsTests。 選取 [位置] 欄位旁邊的瀏覽圖示,然後選取 [WatchPortalFunction] 資料夾。

  4. 選取 [下一步] 。 [其他資訊] 視窗隨即顯示。

  5. 在 [目標 Framework] 下。 接受 [.NET 6.0 (長期支援)] 的預設值。

  6. 選取 建立

  7. 新增專案之後,以滑鼠右鍵按一下 [方案總管] 視窗中的 WatchFunctionTests 專案,然後選取 [管理 NuGet 套件]

  8. 在 [NuGet: WatchFunctionTests] 視窗中,選取 [瀏覽] 索引標籤。在 [搜尋] 方塊中,輸入 Microsoft.AspNetCore.Mvc。 選取 Microsoft.AspNetCore.Mvc 套件,然後選取 [安裝]

    [NuGet 套件管理員] 視窗的影像,使用者正在安裝 Microsoft.Web.RedisSessionStateProvider 套件。

    注意

    測試專案會建立模擬 HTTP 環境。 執行此動作所需的類別位於 Microsoft.AspNetCore.Mvc 套件中。

  9. 請等候此套件安裝完成。 如果出現 [預覽變更] 訊息方塊,請選取 [確定]。 在 [接受授權] 訊息方塊中,選取 [我接受]

  10. 新增套件之後,在 [方案總管] 視窗的 WatchFunctionsTest 專案底下,以滑鼠右鍵按一下 UnitTest1.cs 檔案,然後選取 [重新命名]。 將檔案的名稱變更為 WatchFunctionUnitTests.cs。 在隨即出現的訊息方塊中,若要將 UnitTest1 的所有參考重新命名為 WatchFunctionUnitTests,請選取 [是]

  11. 在 [方案總管] 視窗的 [WatchFunctionsTests] 專案下,以滑鼠右鍵按一下 [相依性],然後選取 [新增專案參考]

  12. 在 [參考管理員] 視窗中,選取 WatchPortalFunction 專案,然後選取 [確定]

新增 WatchInfo 函式的單元測試

您現在可以將單元測試新增至測試專案中。 在豪華鐘錶案例中,您想要確保要求的查詢字串中提供模型時,WatchInfo 函式一律會傳回「正常」回應;如果查詢字串是空的或未包含 model 參數,則傳回「錯誤」回應。

為了確認此行為,您會將一組 Fact 測試新增至 WatchFunctionsTests

  1. 在 [方案總管] 視窗中,按兩下 WatchFunctionUnitTests.cs 檔案以在程式碼視窗中顯示 WatchPortalFunction。

  2. 在檔案頂端,將下列 using 指示詞新增至清單。

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Http.Internal;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Primitives;
    using Microsoft.Extensions.Logging.Abstractions;
    
  3. Test1 方法的名稱變更為 TestWatchFunctionSuccess

  4. TestWatchFunctionSuccess 方法的本文中,新增下列程式碼。 這個陳述式會建立模擬 HTTP 內容和 HTTP 要求。 要求包含查詢字串,其中包含設定為 abcmodel 參數。

    var queryStringValue = "abc";
    var request = new DefaultHttpRequest(new DefaultHttpContext())
    {
        Query = new QueryCollection
        (
            new System.Collections.Generic.Dictionary<string, StringValues>()
            {
                { "model", queryStringValue }
            }
        )
    };
    
  5. 將下列陳述式新增至方法。 此陳述式會建立空的記錄器。

    var logger = NullLoggerFactory.Instance.CreateLogger("Null Logger");
    
  6. 將下列程式碼新增至該方法。 這些陳述式會叫用 WatchInfo 函式,並作為參數傳入空的要求和記錄器。

    var response = WatchPortalFunction.WatchInfo.Run(request, logger);
    response.Wait();
    
  7. 將下列程式碼新增至該方法。 此程式碼會檢查來自函式的回應是否正確無誤。 在此情況下,函式應該會傳回「正常」回應,並在本文中包含預期的資料。

    // 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);
    }
    
  8. 新增兩個名為 TestWatchFunctionFailureNoQueryStringTestWatchFunctionFailureNoModel 的其他方法。 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);
    }
    

執行測試

  1. 在頂端功能表列的 [測試] 下,選取 [執行所有測試]

    Visual Studio 中 [測試] 功能表的螢幕擷取畫面。使用者已選取 [執行] -> [所有測試]。

  2. 在 [測試總管] 視窗中,所有這三個測試都應該成功完成。

    [Team Explorer] 視窗的螢幕擷取畫面。全部三個測試都已成功執行。

  3. 在 [方案總管] 視窗的 WatchPortalFunction 專案底下,按兩下 WatchInfo.cs 以在程式碼編輯器中顯示該檔案。

  4. 尋找下列程式碼。

    // Retrieve the model id from the query string
    string model = req.Query["model"];
    
  5. 變更設定 model 變數的陳述式,如下所示。 此變更會模擬開發人員在程式碼中犯下錯誤。

    string model = req.Query["modelll"];
    
  6. 在頂端功能表列的 [測試] 下,選取 [執行所有測試]。 此時,TestWatchFunctionSuccess 測試應該會失敗。 發生此失敗的原因是 WatchInfo 函式未在查詢字串中找到名為 modelll 的參數,因此函式傳回「錯誤」回應。

    [Team Explorer] 視窗的螢幕擷取畫面。TestWatchFunctionSuccess 測試已失敗。

在本單元中,您會了解如何建立單元測試專案,並實作 Azure 函式的單元測試。