Упражнение. Добавление модульных тестов в приложение

Завершено

В этом уроке мы добавим модульные тесты в автоматическую сборку, созданную с помощью Microsoft Azure Pipelines. Ошибки регрессии ползут в код вашей команды и нарушают функции фильтрации в таблице лидеров. В частности, постоянно отображается неверный игровой режим.

На следующем рисунке показана проблема. Когда пользователь выбирает "Milky Way", чтобы отобразить только оценки из этой игровой карты, они получают результаты от других игровых карт, таких как Andromeda.

A screenshot of the leaderboard showing incorrect results: Andromeda galaxy scores show in the Milky Way galaxy listing.

Команда хочет поймать ошибку, прежде чем она достигнет тестировщиков. Модульное тестирование обеспечивает эффективную реализацию автоматических тестов для поиска регрессионных ошибок.

Добавление модульных тестов на этом этапе в процессе даст команде головной старт, так как они улучшают веб-приложение Space Game . Рекорды и профили игроков в приложении хранятся в базе данных. На текущий момент для тестирования используются расположенные локально данные. Позже планируется подключение приложения к рабочей базе данных.

Для приложений C# существует целый ряд платформ модульного тестирования. Мы будем использовать NUnit, потому что это популярно в сообществе.

Ниже приведен модульный тест, с которым вы работаете:

[TestCase("Milky Way")]
[TestCase("Andromeda")]
[TestCase("Pinwheel")]
[TestCase("NGC 1300")]
[TestCase("Messier 82")]
public void FetchOnlyRequestedGameRegion(string gameRegion)
{
    const int PAGE = 0; // take the first page of results
    const int MAX_RESULTS = 10; // sample up to 10 results

    // Form the query predicate.
    // This expression selects all scores for the provided game region.
    Expression<Func<Score, bool>> queryPredicate = score => (score.GameRegion == gameRegion);

    // Fetch the scores.
    Task<IEnumerable<Score>> scoresTask = _scoreRepository.GetItemsAsync(
        queryPredicate, // the predicate defined above
        score => 1, // we don't care about the order
        PAGE,
        MAX_RESULTS
    );
    IEnumerable<Score> scores = scoresTask.Result;

    // Verify that each score's game region matches the provided game region.
    Assert.That(scores, Is.All.Matches<Score>(score => score.GameRegion == gameRegion));
}

Вы можете выполнить фильтрацию списка лидеров по любому сочетания игрового режима и карты.

Этот тест запрашивает значения рекордов в списке лидеров и проверяет соответствие каждого результата указанной игровой карте.

В методе теста NUnit TestCase предоставляются встроенные данные для тестирования этого метода. Здесь NUnit вызывает метод модульного FetchOnlyRequestedGameRegion теста следующим образом:

FetchOnlyRequestedGameRegion("Milky Way");
FetchOnlyRequestedGameRegion("Andromeda");
FetchOnlyRequestedGameRegion("Pinwheel");
FetchOnlyRequestedGameRegion("NGC 1300");
FetchOnlyRequestedGameRegion("Messier 82");

Обратите внимание на вызов метода Assert.That в конце теста. Утверждение представляет собой условие или инструкцию, которые вы объявляете как истинные. Если соответствующее условие оказывается ложным, это может свидетельствовать о наличии ошибки в коде. NUnit выполняет каждый метод теста с использованием указанных вами встроенных данных и возвращает результат (успешное или неудачное завершение теста).

Многие платформы модульного тестирования используют методы проверки, близкие к естественному языку. Эти методы помогают легко читать и сопоставлять тесты с требованиями приложения.

Рассмотрим утверждение, сделанное в этом примере:

Assert.That(scores, Is.All.Matches<Score>(score => score.GameRegion == gameRegion));

Эту строку можно интерпретировать следующим образом:

Утверждается, что игровая карта каждого возвращаемого значения рекорда соответствует указанной игровой карте.

Ниже приведен процесс.

  1. Получите ветвь из репозитория GitHub, содержащего модульные тесты.
  2. Выполните тесты локально и убедитесь, что они успешно завершаются.
  3. Добавьте в конфигурацию конвейера задачи, позволяющие выполнять тесты и получать их результаты.
  4. Отправьте ветвь в репозиторий GitHub.
  5. Наблюдайте за автоматической сборкой приложения и выполнением тестов в проекте Azure Pipelines.

Извлечение ветви из GitHub

Здесь вы получите unit-tests ветвь из GitHub и проверка из нее или переключитесь на эту ветвь.

Эта ветвь содержит проект Space Game , с которым вы работали в предыдущих модулях, и конфигурацию Azure Pipelines для начала.

  1. В Visual Studio Code откройте интегрированный терминал.

  2. Выполните следующие git команды, чтобы получить ветвь с именем unit-tests из репозитория Майкрософт, а затем переключиться на нее.

    git fetch upstream unit-tests
    git checkout -B unit-tests upstream/unit-tests
    

    Формат этой команды позволяет получить начальный код из репозитория Microsoft GitHub, известного как upstream. Вскоре вы будете отправлять эту ветвь в репозиторий GitHub, известный как origin.

  3. В качестве дополнительного шага откройте файл azure-pipelines.yml в Visual Studio Code и ознакомьтесь с начальной конфигурацией. Конфигурация похожа на базовую, которую вы создали в модуле Создание конвейера сборки в Azure Pipelines. Она предназначена для сборки только конфигурации выпуска приложения.

Локальный запуск тестов

Прежде чем отправлять тесты в конвейер, рекомендуется проводить локальное тестирование. Вы сделаете это здесь.

  1. В Visual Studio Code откройте интегрированный терминал.

  2. Выполните команду dotnet build для сборки каждого проекта в решении.

    dotnet build --configuration Release
    
  3. Выполните следующую dotnet test команду, чтобы запустить модульные тесты:

    dotnet test --configuration Release --no-build
    

    Флаг --no-build указывает на то, что перед запуском проекта не будет выполняться его сборка. Сборка не требуется, поскольку она выполнялась на предыдущем шаге.

    Вы увидите, что все пять тестов проходят.

    Starting test execution, please wait...
    A total of 1 test files matched the specified pattern.
    
    Passed!  - Failed:     0, Passed:     5, Skipped:     0, Total:     5, Duration: 57 ms
    

    В этом примере выполнение тестов занимает менее одной секунды.

    Обратите внимание, что в общей сложности было выполнено пять тестов. Хотя мы определили только один метод теста, FetchOnlyRequestedGameRegionэтот тест выполняется пять раз, один раз для каждой карты игры, как указано в встроенных TestCase данных.

  4. Выполните тесты еще раз. На этот раз укажите параметр --logger, чтобы записывать результаты в файл журнала.

    dotnet test Tailspin.SpaceGame.Web.Tests --configuration Release --no-build --logger trx
    

    В выходных данных, созданных в каталоге TestResults , создается TRX-файл.

    Этот файл представляет собой документ в формате XML, содержащий результаты выполнения теста. Это популярный формат для результатов тестирования, на основании которого Visual Studio и другие средства могут визуализировать результаты.

    Позже вы узнаете, как Azure Pipelines поможет визуализировать и отслеживать результаты теста при выполнении конвейера.

    Примечание.

    TRX-файлы не предназначены для включения в систему управления версиями. Файл gitignore позволяет указать, какие временные и другие файлы, которые нужно игнорировать Git. Файл .gitignore проекта уже настроен таким образом, чтобы игнорировать все содержимое каталога TestResults.

  5. В Visual Studio Code откройте файл DocumentDBRepository_GetItemsAsyncShould.cs из папки Tailspin.SpaceGame.Web.Tests и изучите тестовый код. Даже если вы не планируете создавать приложения .NET, код теста может быть полезным, поскольку аналогичный код может использоваться другими платформами модульного тестирования.

Добавление задач в конфигурацию конвейера

Здесь вы настроите конвейер сборки для выполнения модульных тестов и сбора результатов.

  1. В Visual Studio Code измените azure-pipelines.yml следующим образом:

    trigger:
    - '*'
    
    pool:
      vmImage: 'ubuntu-20.04'
      demands:
      - npm
    
    variables:
      buildConfiguration: 'Release'
      wwwrootDir: 'Tailspin.SpaceGame.Web/wwwroot'
      dotnetSdkVersion: '6.x'
    
    steps:
    - task: UseDotNet@2
      displayName: 'Use .NET SDK $(dotnetSdkVersion)'
      inputs:
        version: '$(dotnetSdkVersion)'
    
    - task: Npm@1
      displayName: 'Run npm install'
      inputs:
        verbose: false
    
    - script: './node_modules/.bin/node-sass $(wwwrootDir) --output $(wwwrootDir)'
      displayName: 'Compile Sass assets'
    
    - task: gulp@1
      displayName: 'Run gulp tasks'
    
    - script: 'echo "$(Build.DefinitionName), $(Build.BuildId), $(Build.BuildNumber)" > buildinfo.txt'
      displayName: 'Write build info'
      workingDirectory: $(wwwrootDir)
    
    - task: DotNetCoreCLI@2
      displayName: 'Restore project dependencies'
      inputs:
        command: 'restore'
        projects: '**/*.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Build the project - $(buildConfiguration)'
      inputs:
        command: 'build'
        arguments: '--no-restore --configuration $(buildConfiguration)'
        projects: '**/*.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Run unit tests - $(buildConfiguration)'
      inputs:
        command: 'test'
        arguments: '--no-build --configuration $(buildConfiguration)'
        publishTestResults: true
        projects: '**/*.Tests.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Publish the project - $(buildConfiguration)'
      inputs:
        command: 'publish'
        projects: '**/*.csproj'
        publishWebProjects: false
        arguments: '--no-build --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)/$(buildConfiguration)'
        zipAfterPublish: true
    
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifact: drop'
      condition: succeeded()
    

    В этой версии представлена задача сборки DotNetCoreCLI@2.

    - task: DotNetCoreCLI@2
      displayName: 'Run unit tests - $(buildConfiguration)'
      inputs:
        command: 'test'
        arguments: '--no-build --configuration $(buildConfiguration)'
        publishTestResults: true
        projects: '**/*.Tests.csproj'
    

    Эта задача сборки выполняет команду dotnet test.

    Обратите внимание, что для этой задачи не указан аргумент --logger trx, который использовался при выполнении тестов вручную. Его автоматически задает аргумент publishTestResults. Если этот аргумент задан, конвейер создает TRX-файл во временном каталоге, доступ к которому осуществляется с помощью встроенной переменной $(Agent.TempDirectory). При этом также публикуются результаты выполнения задачи в конвейере.

    Аргумент projects задает все проекты C#, соответствующие "**/*.Tests.csproj." Часть "**" соответствует всем каталогам, а часть "*.Tests.csproj" соответствует всем проектам, имя файла которых заканчивается на ".Tests.csproj." Ветвь unit-tests содержит только один проект модульного теста, Tailspin.SpaceGame.Web.Tests.csproj. Указав шаблон, можно запускать дополнительные тестовые проекты без необходимости изменять конфигурацию сборки.

Отправка ветви в GitHub

Здесь вы будете отправлять изменения в GitHub и просматривать запуск конвейера. Помните, что сейчас вы находитесь в ветви unit-tests.

  1. В интегрированном терминале добавьте azure-pipelines.yml в индекс, зафиксируйте изменения и отправьте ветвь до GitHub.

    git add azure-pipelines.yml
    git commit -m "Run and publish unit tests"
    git push origin unit-tests
    

Просмотр выполнения тестов в Azure Pipelines

Здесь вы увидите тесты, выполняемые в конвейере, а затем визуализировать результаты из планов тестирования Microsoft Azure. В Azure Test Plans представлены все средства, необходимые для успешного тестирования приложений. Вы можете создавать и запускать планы тестирования вручную, создавать автоматические тесты и собирать отзывы заинтересованных лиц.

  1. В Azure Pipelines выполните трассировку сборки с помощью каждого шага.

    Обратите внимание, что задача Выполнение модульных тестов — выпуск запускает модульные тесты так же, как вы делали это вручную из командной строки.

    A screenshot of Azure Pipelines showing console output from running unit tests.

  2. Вернитесь к сводке конвейера.

  3. Перейдите на вкладку Тесты.

    Появится сводка по выполнению теста. Все пять тестов были успешно завершены.

    A screenshot of Azure Pipelines showing the Tests tab with 5 total tests run and 100 percent passing.

  4. В Azure DevOps выберите "Тестовые планы" и выберите "Запуски".

    A screenshot of Azure DevOps navigation menu with Test Plans section and Runs tab highlighted.

    Появится список недавно запущенных тестов, в том числе и последнего выполненного вами.

  5. Дважды щелкните последний тестовый запуск.

    Появится сводка по результатам выполнения.

    A screenshot of Azure DevOps test run results summary showing 5 passed tests.

    В этом примере все пять тестов были успешно завершены. Если хотя бы один из тестов завершился неудачно, вы можете перейти к задаче сборки и просмотреть дополнительные сведения.

    Кроме того, вы можете скачать TRX-файл, чтобы изучить его с помощью Visual Studio или другого средства визуализации.

Хотя вы добавили только один тест, это хороший запуск, и он устраняет немедленную проблему. Теперь команда может добавлять другие тесты и использовать их для оптимизации общего процесса.

Объединение ветви с главной ветвью

В реальном сценарии, если вы были довольны результатами, вы можете объединить unit-tests ветвь mainс, но для краткости мы пропустим этот процесс на данный момент.