練習 - 修正失敗的測試

已完成

此時,您可以在透過組建管線移動變更時,執行單元測試。 您也可以使用方法來測量測試所涵蓋的程式碼數量。

最好一律在本機執行測試,再將變更提交至管線。 但是當有人忘記並提交中斷組建的變更時,會發生什麼事?

在此單元中,您將會修正因失敗單元測試所造成的中斷組建。 在這裡,您將會:

  • 從 GitHub 取得起始程式碼。
  • 將程式碼涵蓋範圍新增至您的專案。
  • 將程式碼推送到您的存放庫。
  • 觀看管線自動執行和單元測試失敗。
  • 在本機重現失敗。
  • 分析並修正失敗。
  • 推送修正程式,並觀看組建成功。

檢閱新的單元測試

小組的最新功能只涵蓋排行榜。 我們需要從資料庫取得分數,因此,我們可以撰寫單元測試來驗證 IDocumentDBRepository<T>.GetItemsAsync 方法。

測試看起來像這樣。 您還不需要新增任何程式碼。

[TestCase(0, ExpectedResult=0)]
[TestCase(1, ExpectedResult=1)]
[TestCase(10, ExpectedResult=10)]
public int ReturnRequestedCount(int count)
{
    const int PAGE = 0; // take the first page of results

    // Fetch the scores.
    Task<IEnumerable<Score>> scoresTask = _scoreRepository.GetItemsAsync(
        score => true, // return all scores
        score => 1, // we don't care about the order
        PAGE,
        count // fetch this number of results
    );
    IEnumerable<Score> scores = scoresTask.Result;

    // Verify that we received the specified number of items.
    return scores.Count();
}

回想一下,在 NUnit 測試中,TestCase 提供要用來測試該方法的內嵌資料。 NUnit 會呼叫 ReturnRequestedCount 單元測試方法,如下所示:

ReturnRequestedCount(0);
ReturnRequestedCount(1);
ReturnRequestedCount(10);

此測試也會使用 ExpectedResult 屬性來簡化測試程式碼,並協助清除其意圖。 NUnit 會自動比較傳回值與這個屬性的值,而不需明確地呼叫判斷提示。

我們會選擇數個值來表示一般查詢。 我們也會納入 0,以涵蓋該邊緣案例。

從 GitHub 擷取分支

如同您先前所執行的,從 GitHub 擷取 failed-test 分支,並簽出 (或切換至) 該分支。

  1. 在 Visual Studio Code 中,開啟整合式終端。

  2. 執行下列 git fetchgit checkout 命令,以從 Microsoft 存放庫下載名為 failed-test 的分支,並切換至該分支:

    git fetch upstream failed-test
    git checkout -B failed-test upstream/failed-test
    

    我們會將分支命名為 failed-test,以方便學習。 實務上,您會按照分支目的或功能為分支命名。

  3. 執行下列命令以建立本機工具資訊清單檔、安裝 ReportGenerator 工具,以及新增 coverlet.msbuild 套件至您的測試專案:

    dotnet new tool-manifest
    dotnet tool install dotnet-reportgenerator-globaltool
    dotnet add Tailspin.SpaceGame.Web.Tests package coverlet.msbuild
    

    您需要此步驟,因為 failed-test 分支未包含您新增至 unit-tests 分支的工作。

  4. 將您的測試專案檔和工具資訊清單檔新增至暫存索引,並認可您的變更。

    git add Tailspin.SpaceGame.Web.Tests/Tailspin.SpaceGame.Web.Tests.csproj
    git add .config/dotnet-tools.json
    git commit -m "Configure code coverage tests"
    
  5. 執行下列 git push 命令,以將 failed-test 分支上傳至您的 GitHub 存放庫:

    git push origin failed-test
    

查看管線中的測試失敗

假設您最後並未再次執行測試,就匆促地將工作向上推送。 幸運的是,管線可協助我們在進行單元測試時及早揪出問題。 您將在這裡看到該問題。

  1. 從 Azure Pipelines 中,在組建透過管線執行時進行追蹤。

  2. 在其執行時,展開 [執行單元測試 - 版本] 工作。

    您會看到 ReturnRequestedCount 測試方法失敗。

    A screenshot of Azure Pipelines dashboard showing output log of an assertion failure on the unit test, expecting 10 but was 9.

    當輸入值為 0 時測試即會通過,但當輸入值為 1 或 10 時就會失敗。

    只有在上一個工作成功時,才會將組建發佈到管線中。 在這裡,組建因為單元測試失敗而未發佈。 這會防止其他人不小心取得中斷的組建。

實務上,您永遠都不會在組建執行時手動追蹤它們。 以下提供您可能用來探索失敗的數種方式:

  • 來自 Azure DevOps 的電子郵件通知

    您可以設定 Azure DevOps 在組建完成時傳送電子郵件通知給您。 當建置會失敗時,主旨列的開頭為「[組建失敗]」。

    A screenshot of a portion of a build failed email notification.

  • Azure Test Plans

    在 Azure DevOps 中,選取 [測試計畫],然後選取 [執行]。 您會看到最新的測試回合,包括剛執行的測試回合。 選取最新完成的測試。 您會看到八個測試中有兩個失敗。

    A screenshot of Azure DevOps test run outcome showing two of eight failed tests as a ring chart.

  • 儀表板

    在 Azure DevOps 中,選取 [總覽],然後選取 [儀表板]。 您會看到失敗出現在 [測試結果趨勢] Widget 中。 [程式碼涵蓋範圍] 小工具為空白,表示程式碼涵蓋範圍並未執行。

    A screenshot of Azure DevOps dashboard trend chart widget showing two failed test in the last test run.

  • 組建徽章

    雖然 failed-test 分支未在 README.md 中包含組建徽章,但以下是當組建失敗時您會在 GitHub 上看到的內容:

    A screenshot of Azure Pipelines build badge on GitHub indicating a failure.

分析測試失敗

當單元測試失敗時,視失敗的本質而定,您通常會有兩個選擇:

  • 如果測試顯示程式碼中的瑕疵,則修正程式碼並重新執行測試。
  • 如果功能已變更,請調整測試以符合新的需求。

在本機重現失敗。

在本節中,您將在本機重現失敗。

  1. 在 Visual Studio Code 中,開啟整合式終端。

  2. 從終端機中,執行此 dotnet build 命令來建置應用程式:

    dotnet build --configuration Release
    
  3. 從終端機中,執行此 dotnet test 命令來執行單元測試:

    dotnet test --no-build --configuration Release
    

    您應該會看到如同您在管線中所見的相同錯誤。 以下是部分輸出:

    Starting test execution, please wait...
    A total of 1 test files matched the specified pattern.
      Failed ReturnRequestedCount(1) [33 ms]
      Error Message:
         Expected: 1
      But was:  0
    
      Stack Trace:
         at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
       at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.<Execute>b__0()
       at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
    
      Failed ReturnRequestedCount(10) [1 ms]
      Error Message:
         Expected: 10
      But was:  9
    
      Stack Trace:
         at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
       at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.<Execute>b__0()
       at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
    
    
    Failed!  - Failed:     2, Passed:     6, Skipped:     0, Total:     8, Duration: 98 ms
    

尋找錯誤的原因

您注意到每個失敗的測試都會產生差一的結果。 例如,預期 10 時,測試便會傳回 9。

查看所要測試方法 LocalDocumentDBRepository<T>.GetItemsAsync 的原始程式碼。 您應該會看到以下內容:

public Task<IEnumerable<T>> GetItemsAsync(
    Func<T, bool> queryPredicate,
    Func<T, int> orderDescendingPredicate,
    int page = 1, int pageSize = 10
)
{
    var result = _items
        .Where(queryPredicate) // filter
        .OrderByDescending(orderDescendingPredicate) // sort
        .Skip(page * pageSize) // find page
        .Take(pageSize - 1); // take items

    return Task<IEnumerable<T>>.FromResult(result);
}

在此案例中,您可以檢查 GitHub,以查看檔案最近是否變更。

A screenshot of GitHub showing a file diff where a minus one operation was added.

您懷疑 pageSize - 1 所傳回的結果數目少了一個,而這應該只是 pageSize。 在我們的案例中,這是您推送工作而不進行測試時所犯的錯誤,但在真實案例中,您可以洽詢在 GitHub 上變更檔案的開發人員,以判斷變更的原因。

提示

討論與共同作業也可能會在 GitHub 上發生。 您可以在提取要求上加上註解,或提出問題。

修正錯誤

在本節中,您會藉由將程式碼變更回其原始狀態,並執行測試以確認修正來修正此錯誤。

  1. 從 Visual Studio Code,在檔案總管中開啟 Tailspin.SpaceGame.Web/LocalDocumentDBRepository.cs

  2. 修改 GetItemsAsync 方法如下所示:

    public Task<IEnumerable<T>> GetItemsAsync(
        Func<T, bool> queryPredicate,
        Func<T, int> orderDescendingPredicate,
        int page = 1, int pageSize = 10
    )
    {
        var result = _items
            .Where(queryPredicate) // filter
            .OrderByDescending(orderDescendingPredicate) // sort
            .Skip(page * pageSize) // find page
            .Take(pageSize); // take items
    
        return Task<IEnumerable<T>>.FromResult(result);
    }
    

    此版本會將 pageSize - 1 變更為 pageSize

  3. 儲存檔案。

  4. 在整合式終端機中,建置應用程式。

    dotnet build --configuration Release
    

    您應該會看到組建成功。

    在實務上,您可以執行應用程式並短暫地試用。基於學習目的,我們現在會略過該程式。

  5. 在終端機中,執行單元測試。

    dotnet test --no-build --configuration Release
    

    您會看到所有測試通過。

    Starting test execution, please wait...
    A total of 1 test files matched the specified pattern.
    
    Passed!  - Failed:     0, Passed:     8, Skipped:     0, Total:     8, Duration: 69 ms
    
  6. 在整合式終端機中,將每個修改的檔案新增至索引、認可變更,然後將分支推送至 GitHub。

    git add .
    git commit -m "Return correct number of items"
    git push origin failed-test
    

    提示

    在此 git add 範例中的點 (.) 是萬用字元。 其會比對目前目錄和所有子目錄中的所有取消暫存檔案。

    使用這個萬用字元之前,最好在認可之前先執行 git status 以確定您正在暫存您要暫存的檔案。

  7. 返回至 Azure Pipelines。 觀看變更在管線中移動。 測試會通過,而整體組建會成功。

    (選擇性) 若要驗證測試結果,您可以在組建完成時,選取 [測試] 和 [程式碼涵蓋範圍] 索引標籤。

    您也可以查看儀表板,以檢視更新的結果趨勢。

    A screenshot of Azure DevOps dashboard trend chart widget showing a return to all tests passing.

太棒了! 您已修正組建。 接下來,您將會了解如何清除 Azure DevOps 環境。