dotnet-testing-orchestrator 四階段流程深入解析 — 運作機制與實戰演示

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

前言

上一篇拆解了 dotnet-testing-orchestrator 的架構設計 — 1 + 4 的分工策略、HARD STOP 規則、Context Window 的使用效益。但架構圖終究只是設計,真正重要的是:每個 Subagent 實際上做了什麼?它在實際使用中表現如何?

說實話,在第一次按下 Enter 讓 Orchestrator 跑起來的時候,我是很忐忑的。畢竟 4 個 Subagent 要在各自獨立的 Context Window 中接力完成工作,任何一個環節出錯都會影響最終結果。

這篇文章會深入每個 Subagent 的運作機制,並用一個具體的案例帶你走過四階段流程的完整過程 — 從一句話到可執行的完整測試。


Demo 情境設定

被測類別:OrderProcessingService

選擇 OrderProcessingService 作為展示案例,因為它涵蓋了多種測試技術需求:

public class OrderProcessingService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IPaymentGateway _paymentGateway;
    private readonly IEmailService _emailService;
    private readonly TimeProvider _timeProvider;

    public OrderProcessingService(
        IOrderRepository orderRepository,
        IPaymentGateway paymentGateway,
        IEmailService emailService,
        TimeProvider timeProvider)
    {
        _orderRepository = orderRepository;
        _paymentGateway = paymentGateway;
        _emailService = emailService;
        _timeProvider = timeProvider;
    }

    // 多個業務方法...
}

這個類別的特徵:

  • 4 個建構子依賴(3 個介面 + 1 個 TimeProvider)
  • 需要 NSubstitute Mock 3 個介面
  • 需要 FakeTimeProvider 處理時間相關邏輯
  • 有多個分支路徑(成功、失敗、邊界條件)

使用者操作

在 VS Code 中:

  1. 開啟 Chat(Ctrl + Shift + I
  2. 切換到 Agent Mode
  3. 在 Agent 選擇器中選擇 dotnet-testing-orchestrator
  4. 輸入:「為 OrderProcessingService 建立完整的單元測試」

就這樣,一句話。接下來的一切都由 Orchestrator 自動完成。


Phase 1:Analyzer 分析

Orchestrator 收到使用者需求後,第一步是委派 Analyzer 進行程式碼分析。

Analyzer 的運作機制

Analyzer 採用 6 步驟分析流程:

三種目標類型

Analyzer 會將被測類別分為三種類型,這個分類決定了後續 Writer 使用的寫作策略:

目標類型判斷條件Writer 策略
Service一般服務類別,有建構子注入依賴標準 Mock + AAA Pattern
Validator繼承 AbstractValidator<T>(FluentValidation)TestValidate() 方法、對稱驗證覆蓋
Legacy缺少介面注入、static 方法、直接 new 外部依賴Characterization Tests、行為記錄

requiredTechniques — 連結 Analyzer 與 Writer 的關鍵

Analyzer 最重要的輸出之一是 requiredTechniques 列表,這個列表會告訴 Writer 需要載入哪些 Skills。

Analyzer 內部有一張完整的技術對照表,將 19 種技術 ID 對應到具體的偵測條件:

偵測條件requiredTechniques
建構子有介面依賴nsubstitute-mocking
方法有複雜參數autofixture-basics
建構子注入 TimeProviderdatetime-testing-timeprovider
建構子注入 IFileSystemfilesystem-testing-abstractions
繼承 AbstractValidator<T>fluentvalidation-testing
專案有 AutoFixture 自訂 Attributeautofixture-customization
方法回傳複雜物件complex-object-comparison

輸出格式

Analyzer 的完整輸出是一份結構化 JSON 報告,包含:

  • targetType:service / validator / legacy
  • requiredTechniques:技術需求列表
  • suggestedTestScenarios:建議的測試情境(中文三段式命名)
  • existingTestInfrastructure:既有測試基礎設施
  • complexModelAnalysis:複雜物件分析結果
  • fileSystemOperations:檔案系統操作分析
  • timeProviderUsage:TimeProvider 使用分析
  • validatorInfo:Validator 相關資訊(若為 Validator 類型)
  • legacyInfo:Legacy 相關資訊(若為 Legacy 類型)

Demo:OrderProcessingService 的分析結果

OrderProcessingService 為例,Analyzer 自動執行以下分析:

  1. 定位 OrderProcessingService.cs 檔案
  2. 分析建構子依賴:
    • IOrderRepository → 需要 Mock(介面)
    • IPaymentGateway → 需要 Mock(介面)
    • IEmailService → 需要 Mock(介面)
    • TimeProvider → 需要 FakeTimeProvider(特殊依賴)
  3. 分析每個公開方法的簽章、回傳型別、參數、分支數
  4. 判斷目標類型 → Service(非 Validator、非 Legacy)
  5. 掃描既有測試基礎設施(AutoFixture Attribute、共用 Fixture 等)

產出的分析報告:

{
  "targetType": "service",
  "requiredTechniques": [
    "nsubstitute-mocking",
    "autofixture-basics",
    "datetime-testing-timeprovider",
    "awesome-assertions-guide"
  ],
  "existingTestInfrastructure": {
    "hasAutoFixtureAttributes": true,
    "hasFakeTimeProviderExtensions": false,
    "hasSharedFixtures": false
  },
  "suggestedTestScenarios": [
    "ProcessOrder_訂單金額為正值且庫存充足_應成功處理訂單並發送確認信",
    "ProcessOrder_付款閘道回傳失敗_應拋出PaymentFailedException",
    "ProcessOrder_訂單已過期_應回傳訂單過期錯誤",
    "CancelOrder_訂單存在且未出貨_應成功取消並退款",
    "CancelOrder_訂單不存在_應拋出OrderNotFoundException"
  ]
}

關鍵觀察:

  • requiredTechniques 有 4 項 — 這決定了 Writer 會載入 4 個 SKILL.md
  • 偵測到 TimeProvider — 自動標記需要 datetime-testing-timeprovider 技術
  • 掃描到既有 AutoFixture Attribute — Writer 必須重用而非重新建立

Phase 2:Writer 撰寫

Orchestrator 將 Analyzer 的分析報告傳給 Writer,Writer 開始撰寫測試。

Writer 的運作機制

Skills 動態載入策略

Writer 的第一步是根據 requiredTechniques 列表,使用 read 工具明確載入對應的 SKILL.md 檔案。這裡的關鍵是明確使用 read 工具讀取,而不是等待 AI 自動觸發 — 這正是解決第二篇文章提到的觸發問題的核心設計。

我在 Writer 的 .agent.md 裡直接寫了一張對照表,告訴它「看到這個 technique,就去讀這個路徑的 SKILL.md」。不靠 AI 的判斷力,靠明確的指令。這招比改 Description 有效太多了。

Writer 內部有一張完整的技術對應表,把每個 requiredTechniques ID 對應到具體的 SKILL.md 路徑。以下列出幾個常見的對應:

requiredTechniquesSKILL.md 路徑
nsubstitute-mocking.github/skills/dotnet-testing-nsubstitute-mocking/SKILL.md
autofixture-basics.github/skills/dotnet-testing-autofixture-basics/SKILL.md
datetime-testing-timeprovider.github/skills/dotnet-testing-datetime-testing-timeprovider/SKILL.md
filesystem-testing-abstractions.github/skills/dotnet-testing-filesystem-testing-abstractions/SKILL.md
fluentvalidation-testing.github/skills/dotnet-testing-fluentvalidation-testing/SKILL.md
complex-object-comparison.github/skills/dotnet-testing-complex-object-comparison/SKILL.md
......(共 19 個對應,完整表格見 Writer 的 .agent.md

一般情況下,Writer 會載入 3~5 個 SKILL.md,視被測類別的特徵而定。

12 條撰寫規範

Writer 遵循 12 條明確的撰寫規範,這些規範確保產出的測試程式碼品質一致:

  1. 使用 AAA Pattern(Arrange / Act / Assert)並加上對應註解
  2. 中文三段式命名(方法_情境_預期結果)
  3. 使用 AwesomeAssertions(.Should())而非 xUnit Assert.*
  4. AutoFixture-first 資料建構策略
  5. 複雜物件比對使用 BeEquivalentTo()
  6. Validator 測試使用 TestValidate() 方法
  7. Legacy Code 使用 Characterization Tests
  8. 邊界值需加註計算過程
  9. 掃描專案既有測試模式並重用
  10. 不引入未使用的 using 指令
  11. Mock 設定要明確(避免 ReturnsForAnyArgs
  12. Legacy Code 測試命名需保持一致性

工作流程

 

Writer 不會 build 或執行測試 — 這是 Executor 的職責。這個分離確保每個 Subagent 專注於自己的工作。

Demo:OrderProcessingService 的測試撰寫

Writer 根據 requiredTechniques 使用 read 工具載入 4 個 SKILL.md

  1. .github/skills/dotnet-testing-nsubstitute-mocking/SKILL.md — NSubstitute Mock 設定
  2. .github/skills/dotnet-testing-autofixture-basics/SKILL.md — AutoFixture 測試資料產生
  3. .github/skills/dotnet-testing-datetime-testing-timeprovider/SKILL.md — TimeProvider 時間測試
  4. .github/skills/dotnet-testing-awesome-assertions-guide/SKILL.md — AwesomeAssertions 斷言

讀取完 Skills 之後:

  1. 掃描專案中既有的測試模式(發現有 AutoFixture 自訂 Attribute,決定重用)
  2. 讀取 OrderProcessingService.cs 和所有依賴介面的原始碼
  3. 依照 12 條撰寫規範產出測試程式碼

Writer 產出 25 個測試方法,涵蓋:

  • 正常路徑(成功處理訂單、成功取消訂單)
  • 例外路徑(付款失敗、訂單不存在、訂單過期)
  • 邊界條件(金額為零、庫存剛好足夠、時間邊界)
  • TimeProvider 相關測試(使用 FakeTimeProvider 控制時間)

每個測試都遵循中文三段式命名、AAA Pattern、AwesomeAssertions 斷言。


Phase 3:Executor 執行

Writer 完成後,Orchestrator 委派 Executor 執行測試。

Executor 的運作機制

Build-first 工作流

Executor 載入的 dotnet-test Skill 並非本專案自製,而是引用社群中 Oleksii Nikiforov 發佈的 dotnet-test Skill。這個 Skill 定義了 dotnet builddotnet test 的標準執行流程,包含 -p:WarningLevel=0 降噪、--no-build 避免重複編譯等最佳實踐。它不計入本專案的 29 個 Skills 數量中,但是 Executor 運作時的重要依賴。

它定義了 Build-first 策略,先確認編譯通過再執行測試:

常見錯誤修正模式

Executor 的 .agent.md 中記錄了常見的錯誤修正模式,讓 AI 能快速定位問題:

編譯錯誤:

錯誤碼常見原因修正方式
CS0246缺少 NuGet 套件或 using加入缺少的套件引用
CS1061方法名稱拼錯或不存在對照原始碼修正方法名
CS0029型別轉換錯誤修正回傳型別的處理方式
CS7036建構子參數數量不符補齊缺少的 Mock 依賴

測試失敗:

失敗類型常見原因修正方式
斷言不符預期值與實際值不同檢查業務邏輯,修正預期值
NSubstitute 例外Mock 了具體類別而非介面改為 Mock 介面
時區問題未使用 FakeTimeProvider改用 TimeProvider 注入

防幻覺機制

這條規則是我在早期測試時加上的。有一次 AI 回報「25/25 測試通過」,但我實際去跑 dotnet test 卻發現只有 20 個測試,而且有 3 個失敗。AI 把預期的結果當成了實際結果回報 — 這就是幻覺。

所以 Executor 現在有一條明確的規則:所有回報的測試名稱和結果必須來自實際的 dotnet test 輸出,不允許憑空編造。

Demo:OrderProcessingService 的測試執行

Step 1:載入 dotnet-test Skill ✓

Step 2:dotnet build -p:WarningLevel=0 /clp:ErrorsOnly
        → 編譯成功 ✓

Step 3:dotnet test --no-build --verbosity minimal
        → 25/25 通過 ✓(0 輪修正)

回報結果給 Orchestrator

在這個案例中,Writer 產出的測試一次通過,不需要任何修正。說實話,我看到 25/25 全過的時候是有點驚訝的 — 因為在沒有 Orchestrator 的時代,AI 寫出的測試常常有編譯錯誤或型別不匹配。這也反映了 Skills 指導的效果 — 因為 Writer 依照 SKILL.md 的最佳實踐撰寫,編譯和執行錯誤的機率大幅降低。


Phase 4:Reviewer 審查

最後,Orchestrator 委派 Reviewer 進行品質審查。

Reviewer 的運作機制

Skills 載入策略

與 Writer 的動態載入不同,Reviewer 採用固定 + 條件的載入策略:

固定載入(每次都載入):

Skill用途
test-naming-conventions審查命名規範
awesome-assertions-guide審查斷言品質
unit-test-fundamentals審查測試結構

條件載入(視情況載入):

Skill載入條件
nsubstitute-mocking測試中使用了 NSubstitute
complex-object-comparison測試中有複雜物件比對
code-coverage-analysis需要評估覆蓋率完整性

六大審查維度

Reviewer 對測試程式碼進行六個面向的品質審查:

品質評分

Reviewer 輸出結構化 JSON 報告,包含整體評分:

評級標準
A+所有維度都優秀,無 error 或 warning
A整體優秀,少量 warning
A-良好,有改善空間
B+合格,有明確問題需修正
B合格但品質不足
C ~ D品質不足,需要大幅改善

報告中每個問題都標記嚴重等級(error / warning / suggestion),並附帶具體的修正建議和正面亮點。

Demo:OrderProcessingService 的品質審查

Reviewer 載入的 Skills:

  • 固定載入:test-naming-conventionsawesome-assertions-guideunit-test-fundamentals
  • 條件載入:nsubstitute-mocking(因為測試使用了 NSubstitute)

審查結果(節錄):

嚴重等級審查維度發現問題修正建議
⚠️ warning命名品質Test_Process_Success 未使用中文三段式命名改為 處理訂單_正常訂單_應回傳成功結果
⚠️ warning斷言品質使用了 Assert.Equal 而非 AwesomeAssertions改為 result.Should().Be(expected)
💡 suggestion測試結構Arrange 區塊過長,可讀性降低將共用設定提取到 TestBase 或使用 AutoFixture.Customize
✅ passMock 品質僅 Mock 介面,Received() 驗證正確
✅ pass覆蓋率正常、邊界、例外路徑都有覆蓋

整體評分:B+(有明確問題需修正)


完整流程時序

從使用者輸入一句話到收到完整測試和品質報告的完整流程:


整體評分:B+(有明確問題需修正)


使用者體驗對比

最後做一個對比,從使用者的角度來看三種方式的差異:

方法使用者需做的事
純 Skills手動分析需要哪些技術、祈禱 AI 載入正確 Skill
Custom Prompts從 16 個指令中選擇正確的
Orchestrator輸入一句話

Orchestrator 的核心價值就在這裡:使用者不需要理解 29 個 Skills 的分類、不需要從 16 個 Prompts 中挑選正確的指令,只需要告訴 AI 「我要為哪個類別建立測試」

剩下的一切 — 分析程式碼特徵、選擇正確的技術、載入對應的 Skills、撰寫測試、執行驗證、品質審查 — 全部由 Orchestrator 自動完成。


驗證 dotnet-testing-orchestrator

你可以透過我所準備的操作指南以及 Practice 專案(.NET8.0, .NET9.0, .NET10.0)在 VS Code 裡使用 Github Copilot 操做驗證

實際操作執行記錄

指定單一類別撰寫單元測試

完整記錄截圖

指定多個類別撰寫單元測試

在 Phase 1 (Analyzer)、Phase 2 (Weiter)、Phase 3 (Reviewer) 會平行委派同時執行

完整記錄截圖

P.S. 小提示

一回合的整個四階段都執行完成後,Reviewer 如果所提供的「品質檢查」不是 A 或 A+ 或者有提供建議,可以在 Agent 仍然是 dotnet-testing-orchestrator 的狀況下,直接輸入指令「依據 Reivewer 所提供的建議內容進行修改」,Agent 就會對之前第一回合依據 Reviewer 的修改建議進行修改。


小結

這篇文章從兩個層面展示了 Orchestrator 的四階段流程 — 先介紹每個 Subagent 的運作機制(Analyzer 的 6 步驟分析、Writer 的 Skills 動態載入與 12 條規範、Executor 的 Build-first 策略與防幻覺機制、Reviewer 的六大審查維度),再用 OrderProcessingService 的實際案例走過完整流程。

對我來說最重要的收穫是:當品質不如預期時,解法是改進 Agent 定義,而不是推翻架構重來。 這讓整個系統的維護成本非常可控。

但單元測試只是故事的一半。實際專案中,整合測試面對的挑戰完全不同 — Docker 容器、DbContext 衝突、API 端點分析。下一篇會介紹 Integration Test Orchestrator,看看同樣的 1 + 4 架構如何適應整合測試的需求。


參考資源

 

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