Integration Test Orchestrator — 整合測試的自動化架構

系列:從鐵人賽到 Agent Orchestration — AI 自動建立 .NET 測試的完整方案(7)

前言

前幾篇介紹了 Unit Test Orchestrator 的架構設計和實戰流程,以及 v2.0.0 的改進內容與後續方向。單元測試只是測試金字塔的一層,在實際專案中,整合測試同樣重要。

Integration Test Orchestrator 是原本計畫中就要做的第二個 Orchestrator。架構上參考 Unit Test Orchestrator 的 1 + 4 Subagent 設計,流程與各個 Subagent 的內容則依據 dotnet-testing-agent-skills 中的 dotnet-testing-advanced-* 整合測試相關 Skills 來做設計。整合測試面對的挑戰跟單元測試完全不同——Docker 容器管理、DbContext 註冊衝突、API 端點分析、WebApplicationFactory 設定——這些都需要對應的專屬設計。


為什麼需要單獨的 Integration Test Orchestrator

整合測試 vs. 單元測試的根本差異

面向單元測試整合測試
分析對象Class 的方法API 端點
依賴處理Mock 介面真實服務 + 容器
環境需求Docker、資料庫容器
基礎設施簡單的 Test ClassWebApplicationFactory、Collection Fixture、TestBase
資料庫不涉及DbContext 註冊、連線字串替換
執行前檢查Docker daemon 是否啟動

無法共用的原因

整合測試需要的 Agent 能力完全不同:

  • Analyzer 需要分析 API 端點而非 Class 方法、偵測 DbContext 註冊模式、評估容器需求
  • Writer 需要透過 mcp-local-rag 語意查詢整合測試專屬的 Skills(dotnet-testing-advanced-aspnet-integration-testingdotnet-testing-advanced-testcontainers-database 等)、處理 DbContext 註冊衝突、管理 NuGet 套件
  • Executor 需要在 Build 之前先檢查 Docker 環境、處理容器啟動等待時間
  • Reviewer 需要審查容器管理品質、API 測試特有的品質維度

這些差異不是微調就能解決的,需要完整的重新設計。


Integration Orchestrator 架構

5 個專屬 Agent 定義檔

5 個 Agent 定義檔都放在 .github/agents/ 目錄下:

.github/agents/
├── dotnet-testing-advanced-integration-orchestrator.agent.md
├── dotnet-testing-advanced-integration-analyzer.agent.md
├── dotnet-testing-advanced-integration-writer.agent.md
├── dotnet-testing-advanced-integration-executor.agent.md
└── dotnet-testing-advanced-integration-reviewer.agent.md

架構對比

沿用同樣的 1 + 4 Subagent 架構,但管理的 Skills 不同:

Writer 使用的整合測試 Skills

Integration Writer 透過 mcp-local-rag 語意查詢取得 4 個整合測試專屬的 Skills:

Skill用途
dotnet-testing-advanced-aspnet-integration-testingASP.NET Core 整合測試(WebApplicationFactory)
dotnet-testing-advanced-webapi-integration-testingWebAPI 整合測試(HTTP 端點測試)
dotnet-testing-advanced-testcontainers-databaseTestcontainers 資料庫(SQL Server、PostgreSQL)
dotnet-testing-advanced-testcontainers-nosqlTestcontainers NoSQL(Redis、MongoDB)

JSON 交接機制

v2.0.0 引入了結構化 JSON 交接機制,各 Subagent 的中間結果不再透過 prompt 傳遞,而是寫入本地檔案,下游 Subagent 直接讀檔:

.orchestrator/{TargetName}/
├── analyzer-result.json   # Analyzer 的分析報告
├── writer-result.json     # Writer 的輸出摘要
└── executor-result.json   # Executor 的執行結果(固定 schema)

這讓每個階段的 prompt 保持精簡,不會因為 handoff 文字累積而膨脹。

Orchestrator 的運作機制

在多次驗證迭代後,Integration Orchestrator 積累了幾個重要的運作機制:

分階段委派判斷

當 Analyzer 回傳的 suggestedTestScenarios 超過 15 個時,Orchestrator 會自動將 Writer 委派拆為兩次 — 第一次只建立基礎設施(GlobalUsings、WebApiFactory、IntegrationTestBase、.csproj 更新),第二次才撰寫測試案例。這是因為測試案例數量過多時,Writer 若試圖在單次回應中產出所有檔案,會超出 LLM 輸出 token 上限而截斷。

多目標支援

當使用者一次指定多個 Controller 時,Orchestrator 會對每個目標分別執行完整的四階段流程。Analyzer 和 Writer 可以平行執行(各目標獨立),但 Executor 必須循序執行(避免同專案 dotnet build 並行衝突與容器 port 衝突)。

執行進度顯示

每次委派 subagent 前,Orchestrator 會輸出明顯的階段標題(如 ## 階段 1:委派分析(Integration Analyzer)),收到回傳後輸出過渡摘要再進入下一階段,讓使用者清楚掌握執行進度。

Production Code Bug 發現

整合測試的核心價值之一是發現 Production Code 中的真實 Bug。當 Executor 回報修正了 Production Code(非測試程式碼),Orchestrator 會在最終結果中特別標記此發現 — 這是整合測試 ROI 的直接證明。

Phase Timing

v2.0.0 在 Orchestrator 中內建了階段耗時記錄,每次完整流程結束後會輸出 integration-orchestrator-timing.log,顯示各階段耗時:

Phase 1 (Analyzer):  xx 秒
Phase 2 (Writer):    x 分 xx 秒
Phase 3 (Executor):  xx 秒
Phase 4 (Reviewer):  xx 秒
─────────────────────
Total:               x 分 xx 秒

四個 Subagent 的差異比較

Analyzer 差異

整合測試的 Analyzer 與單元測試版本有根本性的不同:

面向Unit Test AnalyzerIntegration Analyzer
分析對象Class 的建構子和方法API 端點(Controller Action)
分類維度Service / Validator / LegacyAPI 架構類型(Minimal API / Controller)
依賴分析介面注入、TimeProviderDbContext、容器需求、Middleware
特殊分析Complex Model、IFileSystemDbContext 註冊模式、Type 衝突風險
環境偵測TargetFramework、testFramework(projectContext)

DbContext 註冊模式分析

這是我在開發 Integration Orchestrator 時踩過最大的坑,也是 Analyzer 最關鍵的新增能力。它會分析 Program.cs 中 DbContext 的註冊方式,分為三種模式:

模式說明風險程度
hardcoded-unconditional直接 AddDbContext 沒有條件判斷高 — 會與測試的 DbContext 註冊衝突
conditionalif 條件判斷環境低 — 測試可以用不同環境繞過
no-registrationProgram.cs 中沒有註冊 DbContext無風險

這個分析結果會直接影響 Writer 的 DbContext 衝突處理策略。

Type 衝突風險分析

當專案使用 Redis 等外部套件時,可能會有 Type 名稱衝突的風險(例如 StackExchange.Redis.Order 與業務 Model Order)。Analyzer 會掃描並標記這些潛在衝突,讓 Writer 在撰寫時使用完整的 namespace 避免問題。

目標專案環境偵測

Analyzer 在分析初期會自動偵測目標專案的 TargetFramework(如 net8.0net9.0net10.0),並將測試框架固定為 xunit。這個資訊會寫入 projectContext 欄位,確保下游 Writer 使用正確的版本號,不會因 LLM 記憶偏差而寫入錯誤的框架版本。

輸出格式

Integration Analyzer 的 JSON 報告比單元測試版本多了幾個關鍵欄位:

{
  "endpointsToTest": [...],
  "dbContextInfo": {
    "hasDbContext": true,
    "dbContextName": "AppDbContext",
    "provider": "SqlServer"
  },
  "dbRegistrationAnalysis": {
    "pattern": "hardcoded-unconditional",
    "riskLevel": "high"
  },
  "containerRequirements": {
    "sqlServer": true,
    "redis": false
  },
  "typeConflictRisks": [...],
  "requiredSkills": [
    "dotnet-testing-advanced-webapi-integration-testing",
    "dotnet-testing-advanced-testcontainers-database"
  ],
  "suggestedTestScenarios": [
    "GetOrders_資料庫有訂單資料_應回傳200OK與訂單清單",
    "CreateOrder_輸入有效訂單_應回傳201Created",
    "CreateOrder_輸入無效訂單_應回傳400BadRequest與驗證錯誤"
  ],
  "projectContext": {
    "targetFramework": "net9.0",
    "testFramework": "xunit",
    "solutionPath": "...",
    "sourceProjectPath": "...",
    "testProjectPath": "..."
  }
}

Writer 差異

Integration Writer 面對的複雜度遠高於單元測試版本。

DbContext 衝突處理 — 三種策略

根據 Analyzer 分析的 DbContext 註冊模式,Writer 選擇不同的處理策略:

Analyzer 偵測結果Writer 策略處理方式
hardcoded-unconditionalStrategy A修改 Program.cs 加入環境條件判斷 + 在測試的 ConfigureServices 中替換
conditionalStrategy B直接在測試的 ConfigureServicesAddDbContext
no-registrationStrategy C直接在測試的 ConfigureServicesAddDbContext

Strategy A 是最複雜的情況,也是開發過程中讓我頭痛最久的問題。當 Program.cs 中有 hardcoded-unconditional 的 DbContext 註冊時,如果測試也嘗試註冊 DbContext,就會發生 DB Provider 衝突 — 兩個 Provider 打架,測試直接爆掉。Writer 的處理方式是:

// Program.cs 中加入環境條件判斷
if (!builder.Environment.IsEnvironment("Testing"))
{
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(connectionString));
}

測試基礎設施建立

Integration Writer 除了撰寫測試外,還需要建立完整的測試基礎設施:

TestProject/
├── Fixtures/
│   └── WebApiFactory.cs          # WebApplicationFactory 封裝
├── TestBase/
│   └── IntegrationTestBase.cs    # 測試基底類別
├── Collections/
│   └── IntegrationTestCollection.cs  # Collection Fixture
└── Controllers/
    └── OrdersControllerTests.cs  # 測試類別

NuGet 套件版本適配

Writer 在撰寫前會執行 dotnet list package --outdated 查詢可升級套件版本。版本決定邏輯:SKILL.md 中的版本號是「最低保證版本」,.csproj 既有版本同樣是版本下限,兩者取較高值,再根據查詢結果升級至同主版號最新穩定版本。禁止 major 升級、禁止降版、禁止使用未經確認的版本號。新增套件後還會二次查詢,確保新增套件也套用相同的升級邏輯。

InMemory 專用 Factory 模式

containerRequirements 為空(純 InMemory 測試)時,Writer 會建立簡化版 Factory — 不需要 IAsyncLifetime、不需要 Container 欄位,僅設定 UseEnvironment("Testing")。資料庫初始化由 IntegrationTestBaseCleanupDatabaseAsync() 負責。

11 條撰寫規範

Integration Writer 有自己的 11 條撰寫規範,與單元測試的 12 條規範不完全相同:

  1. AAA + Cleanup Pattern(測試後清理資料庫)
  2. 中文三段式命名
  3. 使用 AwesomeAssertions(包含 .Be200Ok() 等 Web 專用斷言,不得使用不存在的 .HaveStatusCode()
  4. 使用 WebApplicationFactory(不得 new HttpClient() 或直接建立 TestServer
  5. Collection Fixture 共享容器
  6. 資料庫清理策略(DatabaseManager + Respawn 或 ExecuteSqlRaw 手動清理,依載入的 SKILL 決定)
  7. 使用 System.Net.Http.JsonPostAsJsonAsyncReadFromJsonAsync
  8. ProblemDetails 驗證(使用 Satisfy<T>() 鏈式語法,含複合欄位驗證錯誤與邊界 Happy Path 回應體驗證)
  9. 移除不必要的 using
  10. 測試隔離(每個測試獨立,透過 IntegrationTestBase 清理)
  11. 對稱驗證覆蓋(含條件驗證規則的 null 與空字串邊界對稱)

Executor 差異

Integration Executor 最大的差異在於 Step 0:Docker 環境檢查。執行完成後,Executor 會將結果寫入 .orchestrator/{TargetName}/executor-result.json(固定 schema,包含 totalTestspassedTestsfailedTests 等欄位),供 Reviewer 讀取。

特殊的錯誤修正模式

Integration Executor 除了編譯錯誤和測試失敗外,還需要處理容器相關的錯誤:

錯誤類型常見原因修正方式
Docker daemon 未啟動Docker Desktop 沒開回報使用者,請求啟動 Docker
Port 衝突容器 Port 被佔用使用隨機 Port
DB Provider 衝突DbContext 重複註冊修改 Program.cs 加入條件判斷
DI 容器例外服務註冊衝突調整 ConfigureServices 順序
JSON 序列化失敗API 回傳格式不符預期使用 System.Net.Http.Json

修改 Production Code 的授權

這是一個我猶豫了很久的設計決策。Integration Executor 有兩種被授權修改 Production Code 的情境:

  1. DB Provider 衝突修正 — 當 Program.cshardcoded-unconditional 的 DbContext 註冊,導致測試環境與正式環境的 DB Provider 打架時,Executor 被授權修改 Program.cs 加入 IsEnvironment("Testing") 環境條件判斷。這在 Unit Test Executor 中是不被允許的。
  2. Production Code Bug 修正 — 當整合測試因 Production Code 缺陷而失敗(例如 Controller 未注入已存在的 Validator、缺少必要的 middleware 註冊等),Orchestrator 會授權 Executor 修正 Production Code。此類修正必須在回傳結果中明確標記為「Production Code Bug 修正」,與一般測試程式碼修正區分。

這些授權都是受限的 — 不是讓 Executor 任意修改 Production Code,而是針對整合測試揭露的特定問題給予精確的修正權限。

Executor Fast-Path

與 Unit Test Orchestrator 一致,v2.0.0 在 Integration Executor 中同樣加入了 happy-path 快返規則:首輪 build 成功且 filtered tests 全數通過時,立即回傳固定 schema 的精簡 JSON,不再進行多餘的後置審視步驟。

Reviewer 差異

Integration Reviewer 在原有的六大審查維度基礎上,增加了整合測試特有的面向:

審查維度Unit Test ReviewerIntegration Reviewer
命名品質✅ 中文三段式✅ 中文三段式
斷言品質AwesomeAssertionsAwesomeAssertions + .Be200Ok() 等 Web 斷言
測試結構AAA PatternAAA + Cleanup Pattern
程式碼品質unused usingunused using + System.Net.Http.Json + Factory 封裝性
Mock 品質NSubstituteN/A(整合測試不 Mock)
覆蓋率方法覆蓋端點覆蓋 + 驗證規則對稱覆蓋 + 條件驗證邊界對稱
容器管理ConfigureServices 模式、Container 初始化、WaitStrategy

新增的「容器管理」審查維度包含:

  • ConfigureServices 是否正確 — 支援兩種合法模式:(A) SingleOrDefault descriptor 移除後重新註冊、(B) Program.cs 環境條件判斷 + UseEnvironment("Testing") 直接 AddDbContext。不得使用 ConfigureTestServices
  • Container 是否正確初始化和釋放(readonly 欄位直接初始化,非 nullable)
  • 是否有適當的 WaitStrategy(等待容器就緒),不得使用 Task.Delay() 硬式等待
  • Factory 封裝性 — 所有 Factory 類型(含 InMemory)均不得暴露 public EnsureCreatedAsync() 方法
  • 目錄結構 — 測試專案必須有 Fixtures/TestBase/Controllers/(或 Endpoints/)子目錄結構
  • UseEnvironment("Testing")ConfigureWebHost 中必須呼叫

DbContext 衝突處理實例

這是整合測試中最常遇到的問題。以 SQL Server Testcontainers 場景為例:

問題Program.cs 中有 hardcoded-unconditional 的 DbContext 註冊,測試也嘗試註冊 DbContext 指向 Testcontainers,導致 DB Provider 衝突。

Analyzer 偵測

{
  "dbRegistrationAnalysis": {
    "pattern": "hardcoded-unconditional",
    "riskLevel": "high",
    "location": "Program.cs:12"
  }
}

Writer 套用 Strategy A:修改 Program.cs 加入環境條件判斷,測試中設定 Testing 環境。

Executor 驗證:Build 通過、Test 通過,無 DB Provider 衝突。

這個問題在開發初期就被識別和解決,後續的場景都自動套用正確的策略。


dotnet-testing-advanced-integration-orchestrator - 執行狀態

以下是有開啟 dotnet-testing-skills MCP (mcp-local-rag) 的執行結果


兩個 Orchestrator 的完整對比

面向Unit Test OrchestratorIntegration Orchestrator
Agent 定義檔5 個 dotnet-testing-*.agent.md5 個 dotnet-testing-advanced-integration-*.agent.md
管理的 Skills29 個 Skills(透過 mcp-local-rag 查詢)4 個整合測試 Skills(透過 mcp-local-rag 查詢)
Analyzer 分析對象Class 方法API 端點
Analyzer 特殊分析Complex Model、IFileSystemDbContext 註冊模式、Type 衝突
Writer 基礎設施簡單 Test ClassWebApiFactory、Collection Fixture、TestBase
Executor Step 0Docker 環境檢查
Executor 修改 Production Code不允許授權(DB Provider 衝突 + Production Code Bug 修正)
Reviewer 特殊維度容器管理審查

四個 Orchestrator 的定位

Integration Orchestrator 是 4 個 Orchestrator 中的第二個,每個都沿用 1 + 4 Subagent 架構,但管理不同的 Skills 集合:

Orchestrator測試領域載入的 Skills狀態
Unit Test Orchestrator單元測試29 個 Skills(透過 mcp-local-rag 查詢)+ dotnet-test✅ 驗證完成
Integration Orchestrator整合測試4 個整合測試 Skills(透過 mcp-local-rag 查詢)✅ 本篇
Aspire Testing OrchestratorAspire 測試dotnet-testing-advanced-aspire-testing(透過 mcp-local-rag 查詢)✅ 驗證完成
TUnit Testing OrchestratorTUnit 測試dotnet-testing-advanced-tunit-*(2 個 Skills,透過 mcp-local-rag 查詢)✅ 驗證完成

後兩個 Orchestrator 會在接下來的兩篇分別介紹。


小結

回顧 Integration Orchestrator 的開發過程,最大的體會是:整合測試的複雜度不是「單元測試 + Docker」這麼簡單。DbContext 註冊衝突、Type 名稱衝突、容器管理、對稱驗證覆蓋 — 每一個都是獨立的設計問題,需要在 Agent 定義中明確處理。

經過多次驗證迭代,Agent 定義檔也持續演進 — 加入了分階段委派判斷避免 LLM 輸出截斷、NuGet 版本適配邏輯防止版本偏差、InMemory 專用 Factory 模式簡化無容器場景、以及 Production Code Bug 發現機制。這些都是在實戰中踩坑後回饋到 Agent 定義的改善。

好消息是,1 + 4 Subagent 的架構模式被證明是可以「複製」的。單元測試和整合測試用同樣的架構骨架,但各自填入不同的領域知識。接下來的兩篇會介紹 Aspire Testing Orchestrator 和 TUnit Testing Orchestrator — 同樣的架構模式,但面對截然不同的測試技術。


參考資源

純粹是在寫興趣的,用寫程式、寫文章來抒解工作壓力