关于 dotnet-ado-build-test
dotnet-ado-build-test 为在 Azure DevOps 中配置 .NET 构建与测试流水线提供模式,面向维护 CI 的开发者。内容涵盖 NuGet 源认证、测试与覆盖率报告,以及 net8.0 和 net9.0 的多目标框架矩阵。
主要功能
- 01配置 .NET 构建、测试和打包
- 02认证 Azure Artifacts NuGet 源
- 03发布 TRX 和 JUnit 测试结果
- 04发布 Cobertura 和 JaCoCo 覆盖率
- 05设置多 TFM 矩阵
适用场景
- 01为 Azure DevOps 项目编写构建测试步骤
- 02从 Azure Artifacts 还原 NuGet 包
- 03在流水线中发布测试及覆盖率报告
- 04跨 net8.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 包;使用 PublishTestResults@2 发布 TRX 和 JUnit 格式的测试结果;使用 PublishCodeCoverageResults@2 发布 Cobertura 和 JaCoCo 格式的代码覆盖率;以及在 net8.0 和 net9.0 之间采用多 TFM 矩阵策略。
**版本假设:**使用 DotNetCoreCLI@2 任务(当前版本)。使用 UseDotNet@2 安装 SDK。使用 NuGetAuthenticate@1 访问 Azure Artifacts。使用 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 密钥——该任务会使用流水线的身份。
直接选择源
对于不使用 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 代理为上游源。配置完成后,只需引用一个源即可访问私有包和公共 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会将多个测试项目的结果合并为一次测试运行testRunTitle可在 Azure DevOps 的 Test 选项卡中提供描述性名称
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
代码覆盖率
PublishCodeCoverageResults@2 与 Cobertura
- 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)会在 Azure DevOps 的“生成摘要”选项卡中自动生成 HTML 覆盖率报告,无需使用 reportgenerator。
使用 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)'
在单个作业中为多 TFM 安装多个 SDK
在一个作业中运行所有 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 }}'
Agent 注意事项
- 在多行
script:步骤中使用set -euo pipefail—— Linux 上的 ADOscript:任务默认使用set -e,但不会设置pipefail或nounset;如果不设置pipefail,管道命令中的失败会被悄然忽略。 - 在测试任务上使用
continueOnError: true,不要用于结果发布任务 —— 测试任务不能在结果发布前导致流水线失败,但结果发布任务应反映实际测试结果。 - 为多 TFM 构建安装所有必需的 SDK 版本 —— 如果
dotnet test没有匹配的 SDK,就会产生NETSDK1045;请为每个必需版本添加一个UseDotNet@2步骤。 NuGetAuthenticate@1必须置于还原步骤之前 —— 身份验证令牌会在任务执行时注入 Agent 的 NuGet 配置;如果在身份验证前执行还原,就会因 401 而失败。- 复杂的源配置应使用
feedsToUse: 'config'和nuget.config——feedsToUse: 'select'仅支持一个 Azure Artifacts 源;多源场景需要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 的变量组或流水线级机密变量;硬编码的机密信息会留存在仓库历史记录中。