연습 - 실패한 테스트 수정

완료됨

이 시점에서 변경 사항이 빌드 파이프라인을 통해 이동함에 따라 단위 테스트를 실행할 수 있는 방법이 있습니다. 또한 테스트가 적용되는 코드의 양을 측정할 수도 있습니다.

항상 파이프라인에 변경 내용을 제출하기 전에 로컬로 테스트를 실행하는 것이 좋습니다. 하지만 누군가가 이를 잊고 빌드를 손상시키는 변경 사항을 제출하면 어떻게 될까요?

이 단원에서는 단위 테스트 실패로 인해 손상된 빌드를 수정합니다. 여기서는 다음을 수행합니다.

  • 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에서 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.

테스트 실패 분석

단위 테스트가 실패하는 경우 일반적으로 실패의 특성에 따라 다음과 같은 두 가지 옵션을 선택할 수 있습니다.

  • 테스트 결과 코드에 결함이 있는 것으로 나타나면 코드를 수정하고 테스트를 다시 실행합니다.
  • 기능이 변경된 경우 새 요구 사항에 맞게 테스트를 조정합니다.

로컬로 오류 재현

이 섹션에서는 오류를 로컬에서 재현합니다.

  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이 하나 더 적은 결과를 반환하고 있으며 이는 단지 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 - 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로 돌아갑니다. 파이프라인을 통해 변경 사항이 이동되는 것을 확인합니다. 테스트가 통과되고 전체 빌드가 성공합니다.

    필요에 따라 테스트 결과를 확인하기 위해 빌드 완료 시 테스트코드 검사 탭을 선택할 수 있습니다.

    또한 대시보드를 확인하여 업데이트된 결과 추세를 볼 수도 있습니다.

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

좋습니다! 빌드를 수정했습니다. 다음으로 Azure DevOps 환경을 정리하는 방법을 알아봅니다.