基本 API 應用程式中的單元和整合測試

作者:Fiyaz Bin HasanRick Anderson

整合測試簡介

整合測試評估應用程式元件的層級比單元測試更廣泛。 單元測試可用來測試隔離的軟體元件 (例如個別的類別方法)。 整合測試可確認兩個或多個應用程式元件一起運作以產生預期的結果,可能包括完整處理要求所需的每個元件。

這些更廣泛的測試是用來測試應用程式的基礎結構和整個架構,通常包括下列元件:

  • Database
  • 檔案系統
  • 網路設備
  • 要求-回應管線

單元測試會使用虛構的元件 (稱為虛擬元件 (fake)模擬物件 (mock object)) 來取代基礎結構元件。

對比單元測試,整合測試會:

  • 使用應用程式在生產環境中所使用的實際元件。
  • 需要更多的程式碼和資料處理。
  • 需要更長的時間來執行。

因此,會將整合測試的使用限制在最重要的基礎結構情境中。 如果可以使用單元測試或整合測試來測試行為,請選擇單元測試。

在討論整合測試時,被測試的專案通常稱為待測系統 (或簡稱 "SUT")。 本文通篇使用 "SUT" 來指正在被測試的 ASP.NET Core 應用程式。

對於與資料庫和檔案系統進行資料和檔案存取的測試,不要為每種組合情況都撰寫整合測試。 無論應用程式中有多少部分會與資料庫和檔案系統互動,一組專注的讀取、寫入、更新和刪除整合測試通常能夠充分測試資料庫和檔案系統元件。 可以使用單元測試來對與這些元件互動的方法邏輯進行常規測試。 在單元測試中,使用基礎結構虛擬元件或模擬元件可以加快測試執行速度。

ASP.NET Core 整合測試

ASP.NET Core 中的整合測試需要下列各項:

  • 使用測試專案來包含和執行測試。 測試專案具有對 SUT 的參考。
  • 測試專案會為 SUT 建立一台測試 Web 主機,並使用一台測試伺服器用戶端來處理與 SUT 的要求和回應。
  • 使用一個測試執行器來執行測試並報告測試結果。

整合測試會遵循一系列事件的順序,包括通常的安排 (Arrange)執行 (Act)斷言 (Assert) 測試步驟:

  1. 設定 SUT 的 Web 主機。
  2. 建立測試伺服器用戶端,以將要求提交至應用程式。
  3. 執行安排測試步驟:測試應用程式準備要求。
  4. 執行執行測試步驟:用戶端提交要求並接收回應。
  5. 執行斷言測試步驟:根據預期的回應,驗證實際的回應是通過還是失敗
  6. 程序會持續進行,直到執行完所有的測試為止。
  7. 報告測試結果。

通常,測試 Web 主機的設定與用於測試執行的應用程式的一般 Web 主機不同。 例如,測試可能會使用不同的資料庫或不同的應用程式設定。

基礎結構元件 (例如測試 Web 主機和記憶體內部測試伺服器 (TestServer)) 由 Microsoft.AspNetCore.Mvc.Testing 封裝提供或管理。 使用此封裝可以簡化測試的建立和執行。

Microsoft.AspNetCore.Mvc.Testing 封裝會處理下列工作:

  • 將相依性檔案 (.deps) 從 SUT 複製到測試專案的 bin 目錄中。
  • 內容根目錄設定為 SUT 的專案根目錄,以便在執行測試時找到靜態檔案和頁面/檢視。
  • 提供 WebApplicationFactory 類別來簡化使用 TestServer 引導 SUT 的過程。

單元測試文件說明如何設定測試專案和測試執行器,以及有關如何執行測試的詳細指示與如何命名測試和測試類別的建議。

將單元測試與整合測試分成不同的專案。 分離測試:

  • 協助確保基礎結構測試元件不會意外包含在單元測試中。
  • 允許控制要執行哪一組測試。

GitHub 上的範例程式碼提供基本 API 應用程式上的單元和整合測試範例。

IResult 實作型別

Microsoft.AspNetCore.Http.HttpResults 命名空間中的公用 IResult 實作型別可用來在使用具名方法而非 Lambda 時,對基本路由處理常式進行單元測試。

下列程式碼會使用 NotFound<TValue> 類別:

[Fact]
public async Task GetTodoReturnsNotFoundIfNotExists()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var notFoundResult = (NotFound) result.Result;

    Assert.NotNull(notFoundResult);
}

下列程式碼會使用 Ok<TValue> 類別:

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    context.Todos.Add(new Todo
    {
        Id = 1,
        Title = "Test title",
        Description = "Test description",
        IsDone = false
    });

    await context.SaveChangesAsync();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var okResult = (Ok<Todo>)result.Result;

    Assert.NotNull(okResult.Value);
    Assert.Equal(1, okResult.Value.Id);
}

其他資源