dotnet-ado-build-test について
dotnet-ado-build-test は、Azure DevOps の .NET ビルド・テストパイプライン設定パターンを提供する、CI を保守する開発者向けのスキルです。NuGet フィード認証、テスト・カバレッジ報告、net8.0 と net9.0 のマルチ TFM マトリックスを扱います。
主な機能
- 01.NET のビルド、テスト、パック設定
- 02Azure Artifacts の NuGet フィード認証
- 03TRX と JUnit のテスト結果公開
- 04Cobertura と JaCoCo のカバレッジ公開
- 05マルチ TFM マトリックスの設定
活用シーン
- 01Azure DevOps のビルド・テスト手順の作成
- 02Azure Artifacts からの NuGet パッケージ復元
- 03パイプラインでのテスト・カバレッジ報告
- 04net8.0 と net9.0 でのテスト実行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-ado-build-testインストール後、Claude Code で/dotnet-ado-build-testを入力して読み込まれたか確認します。
詳細
dotnet-ado-build-test
Azure DevOps 向けの .NET ビルドおよびテストパイプラインのパターン:ビルド、テスト、パック操作に使用する DotNetCoreCLI@2 タスク、NuGetAuthenticate@1 を使った Azure Artifacts フィードからの NuGet 復元、TRX および JUnit 形式での PublishTestResults@2 によるテスト結果の公開、Cobertura および JaCoCo 形式での PublishCodeCoverageResults@2 によるコードカバレッジ、さらに net8.0 と net9.0 を対象とする複数 TFM のマトリックス戦略を扱います。
バージョンの前提: DotNetCoreCLI@2 タスク(現行)。SDK のインストールには UseDotNet@2 を使用します。Azure Artifacts には NuGetAuthenticate@1 を使用します。レポートには PublishTestResults@2 と PublishCodeCoverageResults@2 を使用します。
対象範囲の境界: このスキルでは、Azure DevOps の .NET ビルドおよびテストパイプラインの構成を扱います。スターター CI テンプレート(基本的なビルド/テスト/パック)は [skill:dotnet-add-ci] が担当します。コンポーザブルなパイプラインパターン(テンプレート、複数ステージ、トリガー)は [skill:dotnet-ado-patterns] が担当します。テスト戦略のガイダンス(何をテストするか、テストアーキテクチャ、品質ゲート)は [skill:dotnet-testing-strategy] が担当します。ベンチマーク CI ワークフローは [skill:dotnet-ci-benchmarking] が担当します。
対象外: スターター CI テンプレートについては [skill:dotnet-add-ci] を参照してください。テストアーキテクチャと戦略については [skill:dotnet-testing-strategy] を参照してください。CI でのベンチマーク回帰検出については [skill:dotnet-ci-benchmarking] を参照してください。公開とデプロイについては [skill:dotnet-ado-publish] および [skill:dotnet-ado-unique] を参照してください。GitHub Actions のビルド/テストワークフローについては [skill:dotnet-gha-build-test] を参照してください。
相互参照:[skill:dotnet-add-ci] はスターターのビルド/テストテンプレート、[skill:dotnet-testing-strategy] はテストアーキテクチャのガイダンス、[skill:dotnet-ci-benchmarking] はベンチマークの CI 統合を扱います。
DotNetCoreCLI@2 タスク
ビルド
steps:
- task: UseDotNet@2
displayName: 'Install .NET SDK'
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: 'MyApp.sln'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: 'MyApp.sln'
arguments: '-c Release --no-restore'
テスト
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: >-
-c Release
--logger "trx;LogFileName=test-results.trx"
--results-directory $(Build.ArtifactStagingDirectory)/test-results
パック
- task: DotNetCoreCLI@2
displayName: 'Pack NuGet packages'
inputs:
command: 'pack'
packagesToPack: 'src/**/*.csproj'
configuration: 'Release'
outputDir: '$(Build.ArtifactStagingDirectory)/nupkgs'
nobuild: true
カスタムコマンド
タスクで直接サポートされていないコマンド(例:dotnet tool install)の場合:
- task: DotNetCoreCLI@2
displayName: 'Install dotnet tools'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'restore'
複数バージョンの SDK のインストール
複数 TFM のビルド用に、複数バージョンの SDK をインストールします。
- task: UseDotNet@2
displayName: 'Install .NET 8'
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: UseDotNet@2
displayName: 'Install .NET 9'
inputs:
packageType: 'sdk'
version: '9.0.x'
UseDotNet@2 を呼び出すたびに、その SDK バージョンが PATH に追加されます。最後にインストールされたバージョンが既定になりますが、--framework を指定することで、すべてのバージョンを使用できます。
Azure Artifacts フィードからの NuGet 復元
フィード認証に使用する NuGetAuthenticate@1
steps:
- task: NuGetAuthenticate@1
displayName: 'Authenticate NuGet feeds'
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: 'MyApp.sln'
feedsToUse: 'config'
nugetConfigPath: 'nuget.config'
NuGetAuthenticate@1 タスクは、nuget.config で参照されているすべての Azure Artifacts フィードの認証情報を構成します。PAT や API キーを明示的に指定する必要はありません。このタスクではパイプラインの ID が使用されます。
フィードの直接選択
nuget.config を使用しないシンプルな構成では、復元タスクでフィードを直接選択します。
- task: DotNetCoreCLI@2
displayName: 'Restore with Azure Artifacts'
inputs:
command: 'restore'
projects: 'MyApp.sln'
feedsToUse: 'select'
vstsFeed: 'MyProject/MyFeed'
includeNuGetOrg: true
アップストリームソース
Azure Artifacts フィードでは、アップストリームソースとして nuget.org をプロキシできます。設定すると、1 つのフィード参照でプライベートパッケージと公開 NuGet パッケージの両方にアクセスできます。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="MyFeed" value="https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" />
</packageSources>
</configuration>
フィードでアップストリームソースを有効にすると、nuget.org のパッケージは Azure Artifacts フィードにキャッシュされ、すべてのパッケージに対して単一の認証済みソースを利用できます。
組織をまたぐフィードへのアクセス
別の Azure DevOps 組織にあるフィードには、サービス接続を使用します。
- task: NuGetAuthenticate@1
displayName: 'Authenticate external feed'
inputs:
nuGetServiceConnections: 'ExternalOrgFeedConnection'
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: 'MyApp.sln'
feedsToUse: 'config'
nugetConfigPath: 'nuget.config'
テスト結果の公開
TRX 形式での PublishTestResults@2
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: >-
-c Release
--logger "trx;LogFileName=results.trx"
--results-directory $(Common.TestResultsDirectory)
continueOnError: true
- task: PublishTestResults@2
displayName: 'Publish test results'
condition: always()
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '$(Common.TestResultsDirectory)/**/*.trx'
mergeTestResults: true
testRunTitle: '.NET Unit Tests'
主な判断事項:
- テストタスクに
continueOnError: trueを指定すると、テストが失敗した場合でも公開ステップが必ず実行されます - 公開タスクに
condition: always()を指定すると、前のステップの結果にかかわらず実行されます mergeTestResults: trueを指定すると、複数のテストプロジェクトの結果が 1 つのテスト実行にまとめられますtestRunTitleを指定すると、Azure DevOps の「テスト」タブにわかりやすい名前が表示されます
JUnit 形式
一部のサードパーティ製テストフレームワークは JUnit XML を出力します。JUnit 形式を使用します。
- task: PublishTestResults@2
displayName: 'Publish JUnit results'
condition: always()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit-results.xml'
mergeTestResults: true
添付ファイル付きテスト結果
失敗したテストのデバッグ用に、スクリーンショットやログをテスト結果に添付します。
- task: DotNetCoreCLI@2
displayName: 'Run tests with attachments'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: >-
-c Release
--logger "trx;LogFileName=results.trx"
--results-directory $(Common.TestResultsDirectory)
--collect:"XPlat Code Coverage"
continueOnError: true
- task: PublishTestResults@2
displayName: 'Publish test results'
condition: always()
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '$(Common.TestResultsDirectory)/**/*.trx'
mergeTestResults: true
testRunTitle: '.NET Tests'
publishRunAttachments: true
コードカバレッジ
Cobertura を使用した PublishCodeCoverageResults@2
- task: DotNetCoreCLI@2
displayName: 'Test with coverage'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: >-
-c Release
--collect:"XPlat Code Coverage"
--results-directory $(Agent.TempDirectory)/coverage
- task: PublishCodeCoverageResults@2
displayName: 'Publish code coverage'
inputs:
summaryFileLocation: '$(Agent.TempDirectory)/coverage/**/coverage.cobertura.xml'
PublishCodeCoverageResults@2 タスク(v2)は、reportgenerator を使用しなくても、Azure DevOps のビルド概要タブに HTML カバレッジレポートを自動生成します。
ReportGenerator を使用した詳細なカバレッジレポート
組み込みの表示機能では対応できない、カスタムカバレッジレポートを作成するには、次のようにします。
- task: DotNetCoreCLI@2
displayName: 'Test with coverage'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: >-
-c Release
--collect:"XPlat Code Coverage"
--results-directory $(Agent.TempDirectory)/coverage
- script: |
set -euo pipefail
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator \
-reports:$(Agent.TempDirectory)/coverage/**/coverage.cobertura.xml \
-targetdir:$(Build.ArtifactStagingDirectory)/coverage-report \
-reporttypes:HtmlInline_AzurePipelines\;Cobertura
displayName: 'Generate coverage report'
- task: PublishCodeCoverageResults@2
displayName: 'Publish coverage'
inputs:
summaryFileLocation: '$(Build.ArtifactStagingDirectory)/coverage-report/Cobertura.xml'
- task: PublishPipelineArtifact@1
displayName: 'Upload coverage report'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/coverage-report'
artifactName: 'coverage-report'
カバレッジのしきい値
スクリプトのステップで Cobertura XML を解析し、カバレッジの最低基準を適用します。
- script: |
set -euo pipefail
COVERAGE_FILE=$(find $(Agent.TempDirectory)/coverage -name 'coverage.cobertura.xml' | head -1)
COVERAGE=$(python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('$COVERAGE_FILE')
print(float(tree.getroot().attrib['line-rate']) * 100)
")
echo "Line coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "##vso[task.logissue type=error]Coverage ${COVERAGE}% is below 80% threshold"
exit 1
fi
displayName: 'Enforce coverage threshold'
マルチ TFM のマトリックス戦略
TFM とオペレーティングシステムをまたぐマトリックスビルド
jobs:
- job: Test
strategy:
matrix:
Linux_net80:
vmImage: 'ubuntu-latest'
tfm: 'net8.0'
dotnetVersion: '8.0.x'
Linux_net90:
vmImage: 'ubuntu-latest'
tfm: 'net9.0'
dotnetVersion: '9.0.x'
Windows_net80:
vmImage: 'windows-latest'
tfm: 'net8.0'
dotnetVersion: '8.0.x'
Windows_net90:
vmImage: 'windows-latest'
tfm: 'net9.0'
dotnetVersion: '9.0.x'
pool:
vmImage: $(vmImage)
steps:
- task: UseDotNet@2
displayName: 'Install .NET $(dotnetVersion)'
inputs:
packageType: 'sdk'
version: $(dotnetVersion)
- task: DotNetCoreCLI@2
displayName: 'Test $(tfm) on $(vmImage)'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: >-
-c Release
--framework $(tfm)
--logger "trx;LogFileName=$(tfm)-results.trx"
--results-directory $(Common.TestResultsDirectory)
continueOnError: true
- task: PublishTestResults@2
displayName: 'Publish $(tfm) results'
condition: always()
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '$(Common.TestResultsDirectory)/**/*.trx'
testRunTitle: '$(tfm) on $(vmImage)'
1 つのジョブでマルチ TFM を実行するための複数 SDK のインストール
マトリックスではなく、1 つのジョブですべての TFM を実行する場合は、必要な SDK をすべてインストールします。
steps:
- task: UseDotNet@2
displayName: 'Install .NET 8'
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: UseDotNet@2
displayName: 'Install .NET 9'
inputs:
packageType: 'sdk'
version: '9.0.x'
- task: DotNetCoreCLI@2
displayName: 'Test all TFMs'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '-c Release'
対応する SDK がインストールされていない場合、dotnet test はその TFM をビルドできず、NETSDK1045 で失敗します。
再利用可能なテンプレートベースのマトリックス
# templates/jobs/matrix-test.yml
parameters:
- name: configurations
type: object
default:
- tfm: 'net8.0'
dotnetVersion: '8.0.x'
- tfm: 'net9.0'
dotnetVersion: '9.0.x'
jobs:
- ${{ each config in parameters.configurations }}:
- job: Test_${{ replace(config.tfm, '.', '_') }}
displayName: 'Test ${{ config.tfm }}'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: ${{ config.dotnetVersion }}
- task: DotNetCoreCLI@2
displayName: 'Test ${{ config.tfm }}'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '-c Release --framework ${{ config.tfm }}'
エージェントで注意すべき点
- 複数行の
script:ステップではset -euo pipefailを使用します — Linux 上の ADOscript:タスクでは、デフォルトでset -eが有効ですが、pipefailとnounsetは設定されません。pipefailがないと、パイプでつないだコマンドの失敗が黙って無視されます。 - 結果の発行タスクではなく、テストタスクに
continueOnError: trueを設定します — 結果が発行される前にテストタスクでパイプラインが失敗しないようにする一方、発行タスクには実際のテスト結果を反映させる必要があります。 - マルチ TFM ビルドでは、必要な SDK バージョンをすべてインストールします — 対応する SDK がない状態で
dotnet testを実行するとNETSDK1045が発生します。必要なバージョンごとにUseDotNet@2ステップを追加してください。 NuGetAuthenticate@1は復元ステップより前に実行する必要があります — 認証トークンはタスクの実行時にエージェントの NuGet 設定に注入されるため、認証前に復元を実行すると 401 エラーになります。- 複雑なフィード構成では
nuget.configとfeedsToUse: 'config'を使用します —feedsToUse: 'select'がサポートする Azure Artifacts フィードは 1 つのみです。複数フィードを使用する場合は、nuget.configファイルが必要です。 - カバレッジを収集するには
--collect:"XPlat Code Coverage"が必要です — デフォルトのdotnet testではカバレッジファイルは生成されません。XPlat Code Coverageコレクターは .NET SDK に組み込まれています。 PublishCodeCoverageResults@2が受け付けるのは Cobertura XML です — TRX などの形式をカバレッジ発行タスクに渡しても出力は生成されません。コレクターが Cobertura 形式で出力することを確認してください。- ADO のマトリックス構文は GHA と異なります — ADO では配列ではなく、キーと値のペアを持つ名前付きマトリックスエントリを使用します。各エントリで、ジョブ内で使用する変数名をすべて定義する必要があります。
- パイプラインの YAML に認証情報をハードコードしないでください — Azure Key Vault にリンクした変数グループ、またはパイプラインレベルのシークレット変数を使用してください。ハードコードしたシークレットは、リポジトリの履歴から閲覧できます。