关于 dotnet-gha-build-test
用于配置 GitHub Actions 中 .NET 构建与测试流水线的技能,涵盖 SDK、NuGet 缓存与认证、测试及覆盖率报告、多 TFM 测试和测试分片。
主要功能
- 01.NET SDK 设置与 NuGet 认证
- 02NuGet 还原缓存
- 03测试结果发布
- 04覆盖率报告上传
- 05多 TFM 矩阵测试
- 06测试分片策略
适用场景
- 01配置 .NET GitHub Actions 流水线
- 02在 CI 运行间缓存还原结果
- 03发布测试及覆盖率结果
- 04将大型测试套件拆分到多个任务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-gha-build-test安装后在 Claude Code 中输入/dotnet-gha-build-test确认已加载。
详细介绍
dotnet-gha-build-test
适用于 GitHub Actions 的 .NET 构建和测试工作流模式:配置 actions/setup-dotnet@v4,安装多个版本并进行 NuGet 身份验证;缓存 NuGet 还原以加快 CI;使用 dorny/test-reporter 发布 dotnet test 结果;将代码覆盖率上传到 Codecov 和 Coveralls;跨 net8.0 和 net9.0 进行多 TFM 矩阵测试;以及大型项目的测试分片策略。
版本假设: 使用 actions/setup-dotnet@v4 支持 .NET 8/9/10。使用 dorny/test-reporter@v1 可视化测试结果。使用 Codecov 和 Coveralls GitHub Apps 报告覆盖率。
范围界定: 此技能负责 GitHub Actions 的 .NET 构建和测试流水线配置。入门 CI 模板(基本构建/测试/打包)由 [skill:dotnet-add-ci] 负责。可组合的工作流模式(可复用工作流、矩阵策略、缓存)见 [skill:dotnet-gha-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-gha-publish] 和 [skill:dotnet-gha-deploy]。Azure DevOps 构建/测试流水线——参见 [skill:dotnet-ado-build-test]。
交叉引用:[skill:dotnet-add-ci] 提供入门构建/测试模板;[skill:dotnet-testing-strategy] 提供测试架构指导;[skill:dotnet-ci-benchmarking] 提供基准测试 CI 集成;使用集中式构建输出布局时,[skill:dotnet-artifacts-output] 提供制品上传路径调整。
actions/setup-dotnet@v4 配置
基本设置
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
安装多个版本
在单个作业中安装多个 SDK 版本,以支持多 TFM 构建:
- name: Setup .NET SDKs
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
列表中的第一个版本会成为 PATH 上默认的 dotnet。所有已安装的版本都可通过 --framework 指定目标框架。
私有源的 NuGet 身份验证
通过 actions/setup-dotnet@v4 配置 NuGet 源身份验证:
- name: Setup .NET with NuGet auth
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
env:
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
对于多个私有源,请在设置完成后配置其他源:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Add private NuGet feed
run: |
set -euo pipefail
dotnet nuget add source https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json \
--name AzureArtifacts \
--username az \
--password ${{ secrets.AZURE_ARTIFACTS_PAT }} \
--store-password-in-clear-text
在 Linux 运行器上,由于无法使用 DPAPI 加密,因此必须使用 --store-password-in-clear-text 标志。
使用 Global.json 固定 SDK 版本
如果存储库根目录中存在 global.json,actions/setup-dotnet@v4 可以自动读取:
- name: Setup .NET from global.json
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
这样可确保 CI 使用与本地开发相同的 SDK 版本。
NuGet 还原缓存
标准缓存配置
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Restore dependencies
run: dotnet restore MySolution.sln
使用 setup-dotnet 内置缓存
actions/setup-dotnet@v4 支持使用 packages.lock.json 进行内置缓存:
- name: Setup .NET with caching
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
cache: true
cache-dependency-path: '**/packages.lock.json'
请先在本地生成锁定文件:dotnet restore --use-lock-file。提交 packages.lock.json 文件,以确保还原结果可复现。
缓存键策略
| 键组件 | 用途 |
|---|---|
runner.os | 防止不同操作系统之间发生缓存冲突 |
hashFiles('**/*.csproj') | 在包引用发生更改时使缓存失效 |
hashFiles('**/Directory.Packages.props') | 在集中管理的版本发生更改时使缓存失效 |
restore-keys 前缀 | 部分匹配,以增量方式复用缓存 |
发布测试结果
dorny/test-reporter
将 dotnet test 结果作为 GitHub Actions 检查注释发布,并内联显示失败详情:
- name: Test
run: |
set -euo pipefail
dotnet test MySolution.sln \
--configuration Release \
--logger "trx;LogFileName=test-results.trx" \
--results-directory ./test-results
continue-on-error: true
id: test
- name: Publish test results
uses: dorny/test-reporter@v1
if: always()
with:
name: '.NET Test Results'
path: 'test-results/**/*.trx'
reporter: dotnet-trx
fail-on-error: true
关键决策:
- 在测试步骤中设置
continue-on-error: true,确保即使测试失败,报告步骤也始终运行 - 在报告步骤中设置
if: always(),无论测试结果如何都发布结果 - 在报告器中设置
fail-on-error: true,测试失败时将检查标记为失败
替代方案:EnricoMi/publish-unit-test-result-action
如需在 PR 评论中集成更丰富的测试计数信息:
- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: 'test-results/**/*.trx'
check_name: 'Test Results'
上传代码覆盖率
Codecov
- name: Test with coverage
run: |
set -euo pipefail
dotnet test MySolution.sln \
--configuration Release \
--collect:"XPlat Code Coverage" \
--results-directory ./coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
directory: ./coverage
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
Coveralls
- name: Test with coverage
run: |
set -euo pipefail
dotnet test MySolution.sln \
--configuration Release \
--collect:"XPlat Code Coverage" \
--results-directory ./coverage
- name: Upload coverage to Coveralls
uses: coverallsapp/github-action@v2
with:
file: coverage/**/coverage.cobertura.xml
format: cobertura
github-token: ${{ secrets.GITHUB_TOKEN }}
使用 ReportGenerator 生成覆盖率报告
在上传到 CI 的同时生成易于阅读的 HTML 覆盖率报告:
- name: Generate coverage report
run: |
set -euo pipefail
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator \
-reports:coverage/**/coverage.cobertura.xml \
-targetdir:coverage-report \
-reporttypes:HtmlInline_AzurePipelines\;Cobertura
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage-report/
retention-days: 30
多 TFM 矩阵测试
TFM 的矩阵策略
jobs:
test:
strategy:
fail-fast: false
matrix:
tfm: [net8.0, net9.0]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- name: Cache NuGet
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Test ${{ matrix.tfm }}
run: |
set -euo pipefail
dotnet test MySolution.sln \
--framework ${{ matrix.tfm }} \
--configuration Release \
--logger "trx;LogFileName=${{ matrix.tfm }}-results.trx" \
--results-directory ./test-results
- name: Publish test results
uses: dorny/test-reporter@v1
if: always()
with:
name: 'Tests (${{ matrix.os }} / ${{ matrix.tfm }})'
path: 'test-results/**/*.trx'
reporter: dotnet-trx
安装所有必需的 SDK
在单个任务中运行多 TFM 测试,而不是使用矩阵时,请预先安装所有必需的 SDK:
- name: Setup .NET SDKs
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- name: Test all TFMs
run: dotnet test MySolution.sln --configuration Release
如果未安装匹配的 SDK,dotnet test 将无法为该 TFM 构建,并因 NETSDK1045 而失败。
大型项目的测试分片
在并行任务间拆分测试
对于大型测试套件,可将测试项目拆分到并行运行器中,以缩短 CI 总耗时:
jobs:
discover:
runs-on: ubuntu-latest
outputs:
projects: ${{ steps.find.outputs.projects }}
steps:
- uses: actions/checkout@v4
- id: find
shell: bash
run: |
set -euo pipefail
PROJECTS=$(find tests -name '*.csproj' | jq -R . | jq -sc .)
echo "projects=$PROJECTS" >> "$GITHUB_OUTPUT"
test:
needs: discover
strategy:
fail-fast: false
matrix:
project: ${{ fromJson(needs.discover.outputs.projects) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Test ${{ matrix.project }}
run: |
set -euo pipefail
dotnet test ${{ matrix.project }} \
--configuration Release \
--logger "trx;LogFileName=results.trx" \
--results-directory ./test-results
- name: Publish test results
uses: dorny/test-reporter@v1
if: always()
with:
name: 'Tests - ${{ matrix.project }}'
path: 'test-results/**/*.trx'
reporter: dotnet-trx
在项目内按测试类分片
对于单个大型测试项目,可使用 dotnet test --filter 按命名空间拆分:
jobs:
test:
strategy:
fail-fast: false
matrix:
shard: ['Unit', 'Integration', 'EndToEnd']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Test ${{ matrix.shard }}
run: |
set -euo pipefail
dotnet test tests/MyApp.Tests.csproj \
--configuration Release \
--filter "FullyQualifiedName~${{ matrix.shard }}" \
--logger "trx;LogFileName=${{ matrix.shard }}-results.trx" \
--results-directory ./test-results
Agent 注意事项
- 始终在多行 bash
run块中设置set -euo pipefail—— 如果没有pipefail,管道中失败的命令不会传递错误,导致 CI 错误地显示为绿色。 - 在测试步骤而非报告器上使用
continue-on-error: true—— 测试步骤不能提前使任务失败,以便报告器发布结果;但测试失败时,报告器应使检查失败。 - 在 NuGet 缓存键中包含
runner.os—— NuGet 包包含特定于操作系统的原生资源;跨操作系统命中缓存会导致还原失败。 - 为多 TFM 安装所有必需的 SDK 版本 —— 如果没有匹配的 SDK,
dotnet test会产生NETSDK1045;请在dotnet-version中列出所有必需的版本。 - 不要在工作流文件中硬编码 TFM 字符串 —— 使用矩阵变量,使工作流文件与项目配置保持同步;当项目从
net8.0升级到net9.0时,CI 中硬编码的net8.0会导致问题。 - 收集覆盖率需要使用
--collect:"XPlat Code Coverage"—— 默认的dotnet test不会生成覆盖率文件;XPlat Code Coverage收集器已内置于 .NET SDK 中。 - TRX logger 路径必须与报告器的 glob 匹配 —— 如果 logger 将文件写入
test-results/results.trx,报告器的path必须在其 glob 模式中包含该目录。 - 切勿将 NuGet 凭据提交到工作流文件中 —— 所有身份验证令牌都应使用
${{ secrets.* }}引用;标准做法是使用NUGET_AUTH_TOKEN环境变量。