Упражнение. Добавление модульных тестов в приложение
В этом уроке мы добавим модульные тесты в автоматическую сборку, созданную с помощью Microsoft Azure Pipelines. Ошибки регрессии ползут в код вашей команды и нарушают функции фильтрации в таблице лидеров. В частности, постоянно отображается неверный игровой режим.
На следующем рисунке показана проблема. Когда пользователь выбирает "Milky Way", чтобы отобразить только оценки из этой игровой карты, они получают результаты от других игровых карт, таких как Andromeda.
Команда хочет поймать ошибку, прежде чем она достигнет тестировщиков. Модульное тестирование обеспечивает эффективную реализацию автоматических тестов для поиска регрессионных ошибок.
Добавление модульных тестов на этом этапе в процессе даст команде головной старт, так как они улучшают веб-приложение 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));
Эту строку можно интерпретировать следующим образом:
Утверждается, что игровая карта каждого возвращаемого значения рекорда соответствует указанной игровой карте.
Ниже приведен процесс.
- Получите ветвь из репозитория GitHub, содержащего модульные тесты.
- Выполните тесты локально и убедитесь, что они успешно завершаются.
- Добавьте в конфигурацию конвейера задачи, позволяющие выполнять тесты и получать их результаты.
- Отправьте ветвь в репозиторий GitHub.
- Наблюдайте за автоматической сборкой приложения и выполнением тестов в проекте Azure Pipelines.
Извлечение ветви из GitHub
Здесь вы получите unit-tests
ветвь из GitHub и просмотрите или переключитесь на эту ветвь.
Эта ветвь содержит проект Space Game , с которым вы работали в предыдущих модулях, и конфигурацию Azure Pipelines для начала.
В Visual Studio Code откройте интегрированный терминал.
Выполните следующие
git
команды, чтобы получить ветвь с именемunit-tests
из репозитория Майкрософт, а затем переключиться на нее.git fetch upstream unit-tests git checkout -B unit-tests upstream/unit-tests
Формат этой команды позволяет получить начальный код из репозитория Microsoft GitHub, известного как
upstream
. Вскоре вы будете отправлять эту ветвь в репозиторий GitHub, известный какorigin
.В качестве дополнительного шага откройте файл azure-pipelines.yml в Visual Studio Code и ознакомьтесь с начальной конфигурацией. Конфигурация похожа на базовую, которую вы создали в модуле Создание конвейера сборки в Azure Pipelines. Она предназначена для сборки только конфигурации выпуска приложения.
Локальный запуск тестов
Прежде чем отправлять тесты в конвейер, рекомендуется проводить локальное тестирование. Вы сделаете это здесь.
В Visual Studio Code откройте интегрированный терминал.
Выполните команду
dotnet build
для сборки каждого проекта в решении.dotnet build --configuration Release
Выполните следующую
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
данных.Выполните тесты еще раз. На этот раз укажите параметр
--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.
В Visual Studio Code откройте файл DocumentDBRepository_GetItemsAsyncShould.cs из папки Tailspin.SpaceGame.Web.Tests и изучите тестовый код. Даже если вы не планируете создавать приложения .NET, код теста может быть полезным, поскольку аналогичный код может использоваться другими платформами модульного тестирования.
Добавление задач в конфигурацию конвейера
Здесь вы настроите конвейер сборки для выполнения модульных тестов и сбора результатов.
В 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
.
В интегрированном терминале добавьте 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 представлены все средства, необходимые для успешного тестирования приложений. Вы можете создавать и запускать планы тестирования вручную, создавать автоматические тесты и собирать отзывы заинтересованных лиц.
В Azure Pipelines выполните трассировку сборки с помощью каждого шага.
Обратите внимание, что задача Выполнение модульных тестов — выпуск запускает модульные тесты так же, как вы делали это вручную из командной строки.
Вернитесь к сводке конвейера.
Перейдите на вкладку Тесты.
Появится сводка по выполнению теста. Все пять тестов были успешно завершены.
В Azure DevOps выберите "Тестовые планы" и выберите "Запуски".
Появится список недавно запущенных тестов, в том числе и последнего выполненного вами.
Дважды щелкните последний тестовый запуск.
Появится сводка по результатам выполнения.
В этом примере все пять тестов были успешно завершены. Если хотя бы один из тестов завершился неудачно, вы можете перейти к задаче сборки и просмотреть дополнительные сведения.
Кроме того, вы можете скачать TRX-файл, чтобы изучить его с помощью Visual Studio или другого средства визуализации.
Хотя вы добавили только один тест, это хороший запуск, и он устраняет немедленную проблему. Теперь команда может добавлять другие тесты и использовать их для оптимизации общего процесса.
Объединение ветви с главной ветвью
В реальном сценарии, если вы были довольны результатами, вы можете объединить unit-tests
ветвь main
с, но для краткости мы пропустим этот процесс на данный момент.