練習 - 對 Azure 函數進行單元測試
單元測試是一種敏捷式方法的基礎部分。 Visual Studio 提供測試專案範本。 請使用此範本為您的應用程式建立單元測試,您也可以套用相同的技巧來測試 Azure Functions。
在豪華鐘錶線上網站案例中,您的開發小組有一個原則,即在單元測試中達到至少 80% 的程式碼涵蓋範圍。 您想要為 Azure Functions 實作相同的原則。
在這裡,您會了解如何使用 xUnit
測試架構搭配 Visual Studio 來測試 Azure Functions。
建立單元測試專案
第一個步驟是建立包含單元測試的專案,並將其新增至保存 Azure Functions 應用程式的解決方案。 請使用下列步驟來建立單元測試專案,以測試 WatchInfo 函式。
在 Visual Studio 的 [方案總管] 視窗中,以滑鼠右鍵按一下 WatchPortalFunction 方案,選取 [新增],然後選取 [新增專案]。
在 [新增專案] 視窗中,向下捲動並選取 [xUnit 測試專案] C#+ 圖示範本,然後選取 [下一步]。
[設定新專案] 視窗隨即出現。 在 [專案名稱] 欄位中,輸入 WatchFunctionsTests。 選取 [位置] 欄位旁邊的瀏覽圖示,然後選取 [WatchPortalFunction] 資料夾。
選取 [下一步] 。 [其他資訊] 視窗隨即顯示。
在 [目標 Framework] 下。 接受 [.NET 6.0 (長期支援)] 的預設值。
選取 建立。
新增專案之後,以滑鼠右鍵按一下 [方案總管] 視窗中的 WatchFunctionTests 專案,然後選取 [管理 NuGet 套件]。
在 [NuGet: WatchFunctionTests] 視窗中,選取 [瀏覽] 索引標籤。在 [搜尋] 方塊中,輸入 Microsoft.AspNetCore.Mvc。 選取 Microsoft.AspNetCore.Mvc 套件,然後選取 [安裝]。
注意
測試專案會建立模擬 HTTP 環境。 執行此動作所需的類別位於 Microsoft.AspNetCore.Mvc 套件中。
請等候此套件安裝完成。 如果出現 [預覽變更] 訊息方塊,請選取 [確定]。 在 [接受授權] 訊息方塊中,選取 [我接受]。
新增套件之後,在 [方案總管] 視窗的 WatchFunctionsTest 專案底下,以滑鼠右鍵按一下 UnitTest1.cs 檔案,然後選取 [重新命名]。 將檔案的名稱變更為 WatchFunctionUnitTests.cs。 在隨即出現的訊息方塊中,若要將 UnitTest1 的所有參考重新命名為 WatchFunctionUnitTests,請選取 [是]。
在 [方案總管] 視窗的 [WatchFunctionsTests] 專案下,以滑鼠右鍵按一下 [相依性],然後選取 [新增專案參考]。
在 [參考管理員] 視窗中,選取 WatchPortalFunction 專案,然後選取 [確定]。
新增 WatchInfo 函式的單元測試
您現在可以將單元測試新增至測試專案中。 在豪華鐘錶案例中,您想要確保要求的查詢字串中提供模型時,WatchInfo 函式一律會傳回「正常」回應;如果查詢字串是空的或未包含 model
參數,則傳回「錯誤」回應。
為了確認此行為,您會將一組 Fact 測試新增至 WatchFunctionsTests。
在 [方案總管] 視窗中,按兩下 WatchFunctionUnitTests.cs 檔案以在程式碼視窗中顯示 WatchPortalFunction。
在檔案頂端,將下列
using
指示詞新增至清單。using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Logging.Abstractions;
將 Test1 方法的名稱變更為 TestWatchFunctionSuccess。
在 TestWatchFunctionSuccess 方法的本文中,新增下列程式碼。 這個陳述式會建立模擬 HTTP 內容和 HTTP 要求。 要求包含查詢字串,其中包含設定為
abc
的model
參數。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");
將下列程式碼新增至該方法。 這些陳述式會叫用 WatchInfo 函式,並作為參數傳入空的要求和記錄器。
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);
完整的方法應看起來如下。
[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); }
新增兩個名為 TestWatchFunctionFailureNoQueryString 和 TestWatchFunctionFailureNoModel 的其他方法。 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); }
執行測試
在頂端功能表列的 [測試] 下,選取 [執行所有測試]。
在 [測試總管] 視窗中,所有這三個測試都應該成功完成。
在 [方案總管] 視窗的 WatchPortalFunction 專案底下,按兩下 WatchInfo.cs 以在程式碼編輯器中顯示該檔案。
尋找下列程式碼。
// Retrieve the model id from the query string string model = req.Query["model"];
變更設定
model
變數的陳述式,如下所示。 此變更會模擬開發人員在程式碼中犯下錯誤。string model = req.Query["modelll"];
在頂端功能表列的 [測試] 下,選取 [執行所有測試]。 此時,TestWatchFunctionSuccess 測試應該會失敗。 發生此失敗的原因是 WatchInfo 函式未在查詢字串中找到名為
modelll
的參數,因此函式傳回「錯誤」回應。
在本單元中,您會了解如何建立單元測試專案,並實作 Azure 函式的單元測試。