演習 - 失敗したテストを修正する

完了

この時点で、変更がビルド パイプライン間を移動する際に、単体テストを実行する方法があります。 また、テストでカバーされるコードの量を測定する方法があります。

パイプラインに変更を送信する前に、常にローカルでテストを実行することをおすすめします。 しかし、誰かが忘れて、ビルドを中断するような変更を送信した場合はどうなるでしょう。

このユニットでは、単体テストの失敗が原因で失敗したビルドを修正します。 ここでは、次のことを行います。

  • 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 fetch および git 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 コマンドを実行し、GitHub リポジトリに failed-test ブランチをアップロードします。

    git push origin failed-test
    

パイプラインでテストの失敗を確認する

たとえば、あなたは急いでいて最後にもう一度テストを実行せずに作業内容をプッシュしたとします。 幸い、単体テストがある場合、早い段階で問題を見つけるためにパイプラインが役立つ場合があります。 それをここで確認できます。

  1. Azure Pipelines で、パイプラインを介してビルドが実行されているときにビルドをトレースします。

  2. 実行時に Run unit tests - Release タスクを展開します。

    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 で、[Test Plans] を選択してから、[Runs] を選択します。 先ほど実行されたものを含め、最近のテストの実行が示されます。 完了した最新のテストを選択します。 8 つのテストのうち、2 つが失敗したことがわかります。

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

  • ダッシュボード

    Azure DevOps で、[Overview] を選択してから、[Dashboards] を選択します。 [テスト結果の傾向] ウィジェットに失敗が示されているのがわかります。 [Code Coverage] ウィジェットが空白になっており、コード カバレッジが実行されなかったことが示されています。

    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.

テストの失敗を分析する

単体テストが失敗したときに、通常は失敗の性質に応じて、2 つの選択肢があります。

  • テストでコードの不備が明らかになった場合、コードを修正し、テストを再実行します。
  • 機能を変更した場合は、新しい要件が満たされるようにテストを調整します。

ローカルで失敗を再現する

このセクションでは、エラーをローカルで再現します。

  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
    

エラーの原因を見つける

あなたは失敗した各テストが 1 つ少ない結果を生成していることに気付きます。 たとえば、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 が 1 つ少ない結果を返しており、これは pageSize であるべきではないかと考えます。 このシナリオでは、これはテストなしで作業内容をプッシュしたときに発生したエラーですが、実際のシナリオでは、GitHub 上でファイルを変更した開発者とチェックを行い、変更の理由を特定できます。

ヒント

ディスカッションや共同作業が GitHub で行われる場合もあります。 pull request でコメントしたり、問題を開いたりすることができます。

エラーを修正する

このセクションでは、コードを元の状態に戻し、修正を確認するためのテストを実行することで、エラーを修正します。

  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 - 1pageSize に変更します。

  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 に戻ります。 パイプラインでの変更の移動を監視します。 テストが成功し、ビルド全体が成功します。

    必要に応じて、テスト結果を確認するには、ビルドの完了時に [Tests] タブと [Code Coverage] タブを選択します。

    また、ダッシュボードを調べて、更新された結果の傾向を表示することもできます。

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

すばらしい。 ビルドの修正が完了しました。 次は、Azure DevOps 環境をクリーンアップする方法について説明します。