Skip to main content

elf-cicd-backend

Elf Express .NET 10 後端 CI 規範(restore / build / test + coverlet.msbuild 合併行覆蓋率 55% 門檻 + Testcontainers PostgreSQL 18)。 當任務涉及新增或修改 .github/workflows/ci.yml 的 server job、global.json 釘 SDK、 dotnet test 覆蓋率門檻、coverlet 設定、測試專案 csproj、CI 整合測試(Testcontainers)需要 PostgreSQL, 或 .NET 測試在 CI 失敗時觸發。

Quellinformationen

Repository
elf-express/skillshare-source
Letzte Quellaktivität
17. Oktober 2026 um 09:25
Erkannte Sprache von SKILL.md
Chinesisch
Sterne
0
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
4 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
elf-cicd-backend
description
Elf Express .NET 10 後端 CI 規範(restore / build / test + coverlet.msbuild 合併行覆蓋率 55% 門檻 + Testcontainers PostgreSQL 18)。 當任務涉及新增或修改 .github/workflows/ci.yml 的 server job、global.json 釘 SDK、 dotnet test 覆蓋率門檻、coverlet 設定、測試專案 csproj、CI 整合測試(Testcontainers)需要 PostgreSQL, 或 .NET 測試在 CI 失敗時觸發。
metadata
{"version":"1.0.0","owner":"Elf Express"}
# Elf Express 後端 CI(elf-cicd-backend) > 本 skill 只管 `ci.yml` 裡的 `server` job 與 .NET 端的釘版 / 覆蓋率設定。 > 檔頭(`name` / `on`)與 `web` / `e2e` job 見 **elf-cicd-frontend**; > PR AI 審查見 **elf-cicd-review**;版號(含 csproj `<Version>`)見 **elf-cicd-versioning**; > 測試寫法見 **elf-unit** / **elf-integration**;SqlSugar 用法見 **elf-sqlsugar**;NuGet 版本見 **elf-stack**。 ## 1. 何時使用 - 建立或修改 `ci.yml` 的 `server` job - 新增 .NET 測試專案、調整 coverlet / 覆蓋率門檻 - 新增 `global.json` 或升級 .NET SDK - 整合測試需要真的 PostgreSQL(Testcontainers,fixture 由 elf-integration 負責) - `dotnet test` 本機綠、CI 紅 ## 2. 固定規則 **SDK 釘版** 1. MUST 在 repo 根目錄放 `global.json`,`sdk.version` 為 10.0 的 SDK、`rollForward: latestFeature`(見 `templates/global.json`)。 為什麼:沒有 global.json,每台機器用它裝過最新的 SDK,編譯結果與分析器警告會不同。 2. MUST 在 CI 用 `actions/setup-dotnet` v5(SHA 見 elf-stack 3.5) + `global-json-file: global.json`。MUST NOT 用 `dotnet-version:`(不論 `10.0.x`、`latest` 或其他)。 為什麼:SDK 版本只能有一個來源;改 `global.json`,CI 就跟著改。 3. MUST NOT 在 `global.json` 加 `"test": { "runner": "Microsoft.Testing.Platform" }`。 為什麼:本規範的覆蓋率門檻靠 coverlet.msbuild 掛在 VSTest 流程上,切換 runner 後 coverlet 不再插樁,門檻會「靜默消失」。 4. MUST 所有 `actions/*`(checkout / setup-dotnet / upload-artifact …)統一用 v5(SHA 見 elf-stack 3.5)。 **方案與目錄** 5. MUST 使用單一方案檔 `server/<AppName>.slnx`,CI 的 restore / build / test 全部指向它。 為什麼:指向單一專案會漏掉其他測試專案;新增的測試專案沒加進 `.slnx` 就等於沒跑。 6. MUST 主專案放 `server/src/<Project>/`(API 為 `server/src/<AppName>.Api/`);單元測試放 `server/tests/<Project>.Tests/`,一個主專案對應一個 `<Project>.Tests`(見 elf-unit)。 整合測試(需要 Docker,含 contract / `WebApplicationFactory` 測試)MUST 放在獨立的 `server/tests/<AppName>.Api.IntegrationTests/`(見 elf-integration)。 7. MUST 新增測試專案後立刻加入 `.slnx`(`dotnet sln server/<AppName>.slnx add server/tests/<Project>.Tests/<Project>.Tests.csproj`)。 8. 測試 `.csproj` MUST 遵守 Central Package Management:`PackageReference` MUST NOT 帶 `Version=`(版本統一在 `server/Directory.Packages.props`),MUST NOT 自訂 `TargetFramework`(繼承 `server/Directory.Build.props`)。見 `templates/Project.Tests.csproj`。 **步驟順序(固定)** 9. MUST 依序:`dotnet restore` → `dotnet build --no-restore -c Release` → `dotnet test --no-build -c Release` → 合併覆蓋率門檻檢查。 為什麼:分開幾步才看得出是哪一段壞;`--no-restore` / `--no-build` 保證測的就是剛剛 build 出來的那份 Release 產物。 **覆蓋率(本規範選定:coverlet.msbuild + MSBuild 屬性,門檻套在「合併後」的整體)** 10. MUST 在每個測試專案參考 `coverlet.msbuild`,並移除 `dotnet new xunit` 預設帶入的 `coverlet.collector`。 11. MUST 以 solution 層級跑一次 `dotnet test`,帶 `-p:CollectCoverage=true`,並用 `MergeWith` + `-m:1` 把所有測試專案的覆蓋率合併成一份(完整指令見 `templates/ci-server-job.yml` 與 3.2)。 12. MUST 以 `Threshold=55`、`ThresholdType=line`、`ThresholdStat=total` 的語意檢查**合併後**的 `coverage/coverage.cobertura.xml`:整體行覆蓋率 < 55% → CI 紅。 MUST NOT 要求每個測試專案各自達到 55%。為什麼:單元測試與整合測試量的是同一份程式碼的不同面向,逐專案檢查會讓「加起來夠、各自不夠」的專案永遠紅。 只有一個測試專案時,可直接在 `dotnet test` 帶 `-p:Threshold=55 -p:ThresholdType=line -p:ThresholdStat=total`(效果相同);有兩個以上時一律用合併門檻。 13. MUST NOT 把 `branch` 列入門檻。branch 50% 為**建議值**,只由 advisory step 印 `::warning::`。 14. MUST NOT 使用 `coverlet.collector` / `--collect:"XPlat Code Coverage"`。為什麼:它不讀 coverlet.msbuild 的屬性,與 `-p:CollectCoverage=true` 並用還會重複插樁,數字不可信、偶發失敗。 15. MUST NOT 為了過門檻加 `[ExcludeFromCodeCoverage]`、`-p:Exclude=...` 或寫沒有 assert 的測試。排除清單變動必須在 PR 說明理由。 **資料庫(PostgreSQL 18 + SqlSugar 5.x)** 16. 需要真的資料庫的測試 MUST 用 Testcontainers(`postgres:18`,由 elf-integration 的 fixture 啟動)。MUST NOT 在 `ci.yml` 加 `services: postgres`。 為什麼:同一份 fixture 本機與 CI 行為一致;GitHub `ubuntu-latest` runner 內建 Docker,不需要額外設定。 17. MUST 釘 `postgres:18`,MUST NOT 用 `postgres:latest` / `postgres`。為什麼:新 major 會在沒有 PR 的情況下改變測試行為。 18. 測試用拋棄帳密全團隊統一:user `elf`、password `elf_test_pw`、db `elf_test`;連線字串 MUST 含 `Timezone=UTC`。MUST NOT 放真實密碼或 production 連線字串。 要改連既有資料庫(例如除錯)時,由 elf-integration 的 `ELF_TEST_PG_CONNECTION` 環境變數切換;CI 範本不設定它。 19. MUST NOT 在 CI 讓測試連外部共用資料庫。每次 job 都是全新空庫,測試自己建表 / 種資料。 **Job 本身** 20. MUST 維持 job id `server`、`name: server (build · tests)`。為什麼:branch protection 的 required check 以 `name` 比對,改名會讓 PR 永遠等不到狀態。`server` MUST 加進 `ci.yml` 彙總 job `ci` 的 `needs:`(elf-cicd-frontend 規則 23);組織 ruleset 只要求 `ci`,沒列進去的 job 失敗也擋不住發佈。 21. MUST NOT 使用 `paths:` 過濾或 `continue-on-error: true`(理由同 elf-cicd-frontend 規則 24、26)。 22. MUST `uses:` 一律釘 40 碼 commit SHA 並在行尾註解版本,SHA 與版本逐字取自 `stack/elf-stack` 3.5 節對照表;MUST NOT 改回 `@vN`、`@stable` 或分支名。為什麼:見 elf-stack 規則 9 與其 `references/simplification-evidence.md`。 ## 3. 標準範本 | 檔案 | 範本 | 放到 | |------|------|------| | server job | [`templates/ci-server-job.yml`](templates/ci-server-job.yml) | 貼到 `.github/workflows/ci.yml` 的 `jobs:` 底下 | | SDK 釘版 | [`templates/global.json`](templates/global.json) | repo 根目錄 `global.json` | | 測試專案 | [`templates/Project.Tests.csproj`](templates/Project.Tests.csproj)(`ProjectReference` 內的佔位字是 `{Project}`,因為 XML 屬性不能含 `<`) | `server/tests/<Project>.Tests/<Project>.Tests.csproj` | `coverlet.msbuild`、`Microsoft.NET.Test.Sdk`、xUnit、`Microsoft.AspNetCore.Mvc.Testing`、`Testcontainers.PostgreSql` 的版本統一寫在 `server/Directory.Packages.props`(見 elf-stack)。 ### 3.1 目錄結構 ``` global.json server/ ├── <AppName>.slnx ├── Directory.Build.props ← TargetFramework 等共用屬性 ├── Directory.Packages.props ← 所有 NuGet 版本(CPM) ├── src/ │ └── <AppName>.Api/ │ └── <AppName>.Api.csproj ← 含 <Version>,由 scripts/version.mjs 改寫 └── tests/ ├── <AppName>.Api.Tests/ ← 單元測試 │ ├── <AppName>.Api.Tests.csproj │ └── Services/OrderServiceTests.cs ← 資料夾結構比照主專案 └── <AppName>.Api.IntegrationTests/ ← 整合 / contract 測試(需要 Docker) └── <AppName>.Api.IntegrationTests.csproj ``` ### 3.2 本機重現 CI(與 CI 完全相同的指令) 需要 Docker 執行中(整合測試用 Testcontainers 自己起 `postgres:18`)。 ```bash dotnet restore server/<AppName>.slnx dotnet build server/<AppName>.slnx --no-restore --configuration Release rm -rf coverage dotnet test server/<AppName>.slnx --no-build --configuration Release -m:1 \ -p:CollectCoverage=true \ -p:CoverletOutput="$PWD/coverage/" \ -p:MergeWith="$PWD/coverage/coverage.json" \ -p:CoverletOutputFormat=\"json,cobertura\" # 合併後的整體行覆蓋率(門檻 0.55) grep -o -m1 'line-rate="[0-9.]*"' coverage/coverage.cobertura.xml ``` ### 3.3 覆蓋率報告位置 `-p:CoverletOutput` 指到 repo 根目錄的 `coverage/`;每個測試專案跑完都把結果併入 `coverage/coverage.json`, 最後寫出的 `coverage/coverage.cobertura.xml` 就是**所有測試專案合併後**的結果。門檻檢查、advisory branch warning 與 artifact 都只看這一份。 `-m:1` 讓測試專案依序執行,避免多個專案同時寫同一個合併檔。 ## 4. 檢查清單 - [ ] repo 根目錄有 `global.json`,`rollForward: latestFeature`,沒有 `test.runner` - [ ] CI 用 `actions/setup-dotnet` v5(SHA 見 elf-stack 3.5) + `global-json-file: global.json`,沒有 `dotnet-version:` - [ ] 所有 `actions/*` 都是 v5(SHA 見 elf-stack 3.5) - [ ] 所有 restore / build / test 都指向 `server/<AppName>.slnx` - [ ] 單元測試在 `server/tests/<Project>.Tests/`、整合測試在 `server/tests/<AppName>.Api.IntegrationTests/`,且都已加入 `.slnx` - [ ] 測試 csproj 參考 `coverlet.msbuild`,**沒有** `coverlet.collector`、沒有 `Version=`、沒有自己的 `TargetFramework` - [ ] `dotnet test` 帶 `CollectCoverage` + `MergeWith` + `-m:1`;門檻檢查看合併後的 `coverage/coverage.cobertura.xml`(line ≥ 55%) - [ ] 門檻裡沒有 `branch`;branch 只有 advisory warning - [ ] `ci.yml` 沒有 `services: postgres`;DB 測試走 Testcontainers `postgres:18` - [ ] 測試帳密為 `elf` / `elf_test_pw` / `elf_test`,連線字串含 `Timezone=UTC` - [ ] job `name: server (build · tests)` 未變更 - [ ] 本機跑 3.2 的指令全綠,且合併後 line-rate ≥ 0.55 - [ ] 刻意改壞一段被測邏輯,確認測試會紅,再改回來 - [ ] 所有 `uses:` 為 40 碼 SHA+`# 版本` 註解,且與 elf-stack 3.5 對照表逐字一致 ## 5. 常見錯誤 | 症狀 | 原因 | 修正 | |------|------|------| | 覆蓋率明明很低 CI 卻綠 | 用的是 `coverlet.collector`(`--collect`),coverlet.msbuild 的屬性完全沒作用 | 換成 `coverlet.msbuild`(規則 10、14) | | 覆蓋率門檻突然不生效 | `global.json` 被加了 `test.runner: Microsoft.Testing.Platform` | 移除,維持 VSTest(規則 3) | | 整體夠、但某個測試專案單獨報 threshold 失敗 | 在多專案的 solution 上直接帶 `-p:Threshold=55`,每個專案各自被檢查 | 改用 `MergeWith` 合併後再檢查(規則 11、12) | | 合併報告數字忽高忽低 / 檔案損毀 | 沒帶 `-m:1`,多個測試專案同時寫 `coverage.json` | 加 `-m:1` | | 新測試專案的測試 CI 沒跑 | 沒加進 `server/<AppName>.slnx` | `dotnet sln ... add ...` | | 整合測試報 Docker 無法連線 | 在沒有 Docker 的 runner(非 Linux / 自架)上執行 | 用 `ubuntu-latest`(內建 Docker);本機先啟動 Docker | | `NU1008`(CPM 專案不應定義版本) | 測試 csproj 的 `PackageReference` 帶了 `Version=` | 刪掉 `Version=`,版本放 `Directory.Packages.props` | | 本機綠、CI 資料庫行為不同 | 本機 Postgres 版本與 CI / production 不同,或用 `latest` | 全部釘 `postgres:18` | | 時間欄位差 8 小時 | 連線字串沒有 `Timezone=UTC` | 所有連線字串加 `Timezone=UTC` | | SqlSugar 讀資料時 `Activator.CreateInstance` 丟例外 | Entity 用了 C# `required` 成員(參考專案踩過) | 改用屬性預設值(見 elf-sqlsugar) | | 測試在 CI 綠、`dotnet run` 卻找不到檔案 | 程式用相對於「目前目錄」的路徑;測試剛好從輸出目錄跑(參考專案踩過) | 改用 `AppContext.BaseDirectory` | | `dotnet test --no-build` 找不到組件 | build 用 Release、test 沒帶 `--configuration Release` | 每一步都帶同一個 configuration | ## 6. 待確認 - `global.json` 的確切 SDK 版本(範本暫填 `10.0.100`):團隊要釘哪個 feature band / patch 待確認。 - 是否改用 xUnit v3 待確認(版本號一律在 `Directory.Packages.props`,由 elf-stack 維護)。 - 是否把單元測試與整合測試分成兩段執行(例如 `--filter "Category!=Integration"`),待確認;目前範本一次跑全部並合併覆蓋率。 - 覆蓋率摘要是否要貼到 PR(例如 ReportGenerator 產生 `$GITHUB_STEP_SUMMARY`),待確認;目前只上傳 artifact + warning。 - `coverlet.msbuild` 搭配 `dotnet test --no-build` + `MergeWith` 在 .NET 10 SDK 的行為未在實機驗證;首次導入時請確認 CI log 出現 coverlet 的 `Calculating coverage result...`,且 `coverage/coverage.cobertura.xml` 的 line-rate 合理。
Auf GitHub ansehen