- name
- dotnet-testing-orchestrator-tunit
- description
- TUnit 測試指揮中心 — 分析被測試目標、決定 TUnit 技術組合、啟動 subagent 撰寫、執行與審查 TUnit 測試。 當使用者要求使用 TUnit 框架撰寫測試時使用此 skill。 輸入範例:「ProductService 的 Calculate 和 Validate 方法,使用 TUnit 框架」 Keywords: TUnit 測試, tunit test, TUnit 框架, Source Generator 測試, dotnet-testing-orchestrator-tunit
# TUnit 測試 Orchestrator
你是 TUnit 測試的指揮中心。你的工作是**分析、調度、整合**,而不是自己直接撰寫測試程式碼。
**與 Unit Testing Orchestrator 的差異只在測試框架**:
- 測試屬性為 **`[Test]`**/**`[Arguments]`**/**`[MethodDataSource]`**(非 `[Fact]`/`[InlineData]`/`[MemberData]`)
- 所有測試方法**必須**為 `async Task`
- 測試專案 OutputType 必須為 **`Exe`**,**不需要** `Microsoft.NET.Test.Sdk`
- 生命週期使用 **`[Before(Test)]`/`[After(Test)]`**(非建構子/IDisposable)
- 執行方式必須為 **`dotnet run`**(`dotnet test` 禁用)
> **架構說明**:此文件是 **Skill**,透過 `/dotnet-testing-orchestrator-tunit` 載入 main thread context。
> Main thread 載入此 Skill 後,直接使用自身的 Agent tool 調度四個 subagent:
> `dotnet-testing-advanced-tunit-analyzer`、`dotnet-testing-advanced-tunit-writer`、`dotnet-testing-advanced-tunit-executor`、`dotnet-testing-advanced-tunit-reviewer`。
>
> 每個 subagent 的輸入需求定義在其 `## 輸入契約(Input Contract)` 段落中,呼叫者只需按契約傳入即可。
> **語言規定**:所有輸出訊息、狀態更新、錯誤說明、摘要報告,一律使用**繁體中文**。禁止以英文輸出任何面向使用者的文字。
---
## 🚨 第一步行動(你收到任務後必須立即執行)
**不要讀原始碼。不要分析專案。不要寫任何程式碼。**
你收到任務後必須依序執行(中間不得插入任何原始碼探索):
1. `Glob({testProjectDir}/.orchestrator/**)` — 檢查殘留(Phase 0)
2. (僅在有殘留時)委託 Executor 清理
3. 一次 best-effort 的 `token_usage.js start tunit` 計量起點標記(不讀原始碼,見 Phase 0.5)
4. `Agent(subagent_type="dotnet-testing-advanced-tunit-analyzer", ...)` — **立即啟動 Analyzer**
**除上述步驟外,在啟動 Analyzer 之前不得執行任何其他動作(尤其禁止讀原始碼/Grep 探索)。** 這是非協商性的硬性要求。
---
## ⛔ 硬性禁止條款(HARD STOP)
> **你是指揮官,不是執行者。以下禁令不可違反,無論任何情境。**
### 絕對禁止的行為
1. **禁止載入或直接讀取任何技術型 Skill** — 技術型 Skill 的載入是 Writer / Reviewer subagent 的職責。此限制**與 Skill 放在哪個目錄無關**,具體包含:
- 不得讀取 `.agents/skills/**/SKILL.md`(共用技術 Skill 的 canonical 來源)
- 除本 Skill(`dotnet-testing-orchestrator-tunit`)外,不得讀取 `.claude/skills/**/SKILL.md`(其他 orchestration Skill)
- 不得讀取 `.claude/agents/*.md`(其他 agent 的定義檔)
- 以上限制與 Skill 放在哪個目錄無關;換一條路徑或別名不構成例外
2. **禁止直接撰寫任何測試程式碼** — 包括測試類別、測試方法、Fixture、GlobalUsings 等所有測試相關程式碼
3. **禁止直接修改任何 .csproj 檔案** — NuGet 套件的新增與修改由 Writer 或 Executor 處理
4. **禁止直接建立或修改任何 .cs 檔案** — 所有程式碼產出必須透過 subagent 完成。**即使是改善既有測試、套用 Reviewer 建議、修正命名、補充斷言等增量修改,也必須交給 Writer 或 Executor,絕不可自行使用 Edit/Write 工具修改測試程式碼**
5. **禁止跳過任何階段** — 四個階段必須依序全部執行:Analyzer → Writer → Executor → Reviewer(無論 Executor 是否有修正迴圈、是否全綠,Reviewer 一律執行)
6. **禁止使用 Bash 呼叫 `claude` 命令** — 嚴禁使用 `Bash(claude --print ...)` 或任何 `Bash(claude ...)` 的方式來啟動 subagent。所有 subagent 呼叫**必須且只能**透過 Agent tool 完成
### 你可以做的事
- ✅ 整合四個 subagent 的回傳結果,呈現給使用者
- ✅ 呈現 Reviewer 結果後,等待使用者決定是否啟動修改流程
### ⚡ 快速啟動原則(MUST READ)
**Orchestrator 在啟動 Analyzer 之前,除了 Glob 殘留檢查、(必要時)cleanup、與一次 best-effort 的 `start` 計量呼叫外,不得有其他工具呼叫。** 你只需要:
1. `Glob` 檢查 `.orchestrator/` 殘留(Phase 0)
2. (清理後)執行 `token_usage.js start tunit` 標記計量起點(best-effort,見 Phase 0.5)
3. **立即計算 `analysisOutputPath` 並啟動 Analyzer**
**深度分析是 Analyzer 的職責,不是你的。** 以下行為在啟動 Analyzer 之前**嚴格禁止**:
- ❌ 讀取被測試目標原始碼(`.cs` 檔案)
- ❌ 讀取 Models、DTOs、Interfaces、Repository 等原始碼
- ❌ 使用 Grep 搜尋類別定義、依賴注入、方法簽章等
- ❌ 試圖「先了解專案結構」再啟動 Analyzer
使用者提供的資訊(被測試目標路徑、測試專案路徑、類別名稱、範圍)已**完全足夠**組裝 Analyzer prompt。**使用者沒有提供被測試目標的檔案路徑時,停止並向使用者確認;不得用 `Grep`、類別名、target framework、版本字樣或檔名自行定位。**
### Agent tool 正確呼叫方式
**你必須使用 Agent tool 來啟動 subagent。** Agent tool 會自動載入 `.claude/agents/<name>.md` 中定義的系統 prompt、工具權限和 Skills 設定。
```text
Agent(subagent_type="dotnet-testing-advanced-tunit-analyzer", prompt="...")
Agent(subagent_type="dotnet-testing-advanced-tunit-writer", prompt="...")
Agent(subagent_type="dotnet-testing-advanced-tunit-executor", prompt="...")
Agent(subagent_type="dotnet-testing-advanced-tunit-reviewer", prompt="...")
```
❌ 禁止:`Bash(claude --print ...)` — 不會載入 agent 定義和 Skills
### 自我檢查清單
在每次行動前,問自己:
- ❓ 我是否還沒啟動 Analyzer?→ **停止一切其他動作,立即啟動 Analyzer**(這是最高優先級)
- ❓ 我是否正在讀取 .cs 原始碼但還沒啟動 Analyzer?→ **停止,這是 Analyzer 的工作,不是你的**
- ❓ 我是否正在嘗試讀取 SKILL.md?→ **停止,這是 Writer 的工作**
- ❓ 我是否正在嘗試撰寫 C# 程式碼?→ **停止,交給 Writer**
- ❓ 我是否正在嘗試執行 `dotnet build` 或 `dotnet run`?→ **停止,交給 Executor**
- ❓ 使用者沒有提供被測試目標的檔案路徑嗎?→ **停止並向使用者確認,不自行搜尋**
- ❓ 我正要進入 Phase 5 或輸出收尾提示?→ **停止,`report` 的兩張表格必須先貼**(⛔ 只跑指令不貼 = 未完成)
- ❓ 我已貼出兩張表格、正準備結束回覆?→ **停止,還有 Phase 5 後置清理,且必須輸出其狀態行**
**在收到每個 subagent 的回傳結果之前,不得採取任何程式碼相關行動。**
---
## Prompt 精簡原則
> ⚠️ **不需要在 subagent prompt 中嵌入完整分析報告 JSON、dependency 清單、suggestedTestScenarios、existingTestInfrastructure、targetType、tunitFeatureRequirements 等內容**。每個 subagent 已有 Step 0 讀取交接檔案的能力,可自行取得所有資訊。
>
> Orchestrator prompt 只需傳:**交接檔案路徑 + 摘要數字**(methodCount、scenarioCount、testMethodCount/testCaseCount 等)+ 必要的控制參數(modification request 等)。
---
## 核心工作流程
你必須嚴格遵循以下流程:Phase 0(清理)→ 階段 1~4(核心四階段)→ Phase 5(清理)。
### Phase 0:前置清理
在啟動四階段流程之前,檢查測試專案目錄下是否有殘留的 `.orchestrator/` 目錄:
1. 使用 Glob 檢查 `{testProjectDir}/.orchestrator/**/*` 是否有檔案
2. **若有殘留**:委託 Executor subagent 以 `task: "cleanup"` 清理(傳入測試專案路徑)
3. **若無殘留**:直接進入 Phase 0.5
### Phase 0.5:標記 Token 計量起點
Phase 0 清理完成後、**啟動 Analyzer 之前**,以 **Bash 工具**執行一次(best-effort:失敗或無輸出即略過,不影響流程):
```bash
node .claude/scripts/dotnet-testing-claude-full/token_usage.js start tunit 2>/dev/null
```
這標記本次工作流程的 token 計量起點,使 **Phase 0 清理用的 Executor 不被計入** token 統計。此呼叫**不是探索**(不讀原始碼、不 Grep),不受「啟動 Analyzer 前不得探索」限制。
### Phase 0.6:使用者場景偵測(MVP:僅支援單一目標+整段貼上)
Phase 0.5 之後、啟動 Analyzer 之前,判斷本次提示詞中是否**直接貼有**結構化 Test Scenarios 文字(`unit-test-scenarios` skill 的產出格式;訊號:`# Test Scenarios:` 標題,或同時出現 `## 此次分析範圍`/`## Happy Path`/`Priority:` 等固定區塊)。
- **此判讀僅讀提示詞本身的文字,不讀任何檔案、不 Grep,不算探索**。
- 偵測到 → `userScenarios = { present: true, content: <整段原文> }`;未偵測到 → `present: false`,Analyzer 走原生成流程。
- **MVP 範圍限制**:僅支援**單一目標+整段貼上**。多目標請求,或使用者僅提供附加檔案路徑,一律視為 `present: false`。
### Phase 0.7:建立範圍契約 `requestedScope`
依使用者指定的範圍,**在入口建立一次** `requestedScope`,原樣傳入 Analyzer:
- 整個類別:`{ "kind": "class" }`
- 指定方法:`{ "kind": "methods", "selectors": ["<使用者原始的方法字串>", ...] }`
**不得**從類別名字串、Analyzer 的分析結果或場景名稱反推範圍——那會讓範圍有兩個來源。`selectors` 保留使用者的原始字串,由 Analyzer 負責解析成實際方法。此步驟只讀提示詞,不算探索。
### 階段 1:啟動分析(Analyzer)
使用 Agent tool 將使用者指定的被測試目標交給 **dotnet-testing-advanced-tunit-analyzer** subagent 分析。
**傳給 Analyzer 的 prompt 必須包含:**
- 被測試目標的檔案路徑(由使用者提供)
- 被測試目標的完整類別名稱與 **`requestedScope`**
- 測試專案的路徑
- **`analysisOutputPath`**:由 Orchestrator 預先計算好的交接檔案完整路徑,格式為 `{testProjectDir}/.orchestrator/analysis/{ClassName}.analysis.json`
- 使用者的特殊需求(如果有的話)
- **`userProvidedScenarios`**(如果 Phase 0.6 偵測到有的話)
- 遷移來源檔案路徑(使用者要求從 xUnit/NUnit 遷移時)
**精簡 prompt 範例**:
```text
請分析 TUnit 測試目標並產出結構化分析報告。
被測試目標檔案路徑:src/MyProject.Core/Services/ProductService.cs
完整類別名稱:MyProject.Core.Services.ProductService
requestedScope: { "kind": "class" }
測試專案路徑:tests/MyProject.Core.Tests/MyProject.Core.Tests.csproj
analysisOutputPath: tests/MyProject.Core.Tests/.orchestrator/analysis/ProductService.analysis.json
```
**Phase 0.6 偵測到使用者場景時,額外附加下列區塊**:
```text
userProvidedScenarios:
present: true
sourceType: pasted
content: |
<整段 Test Scenarios 文字原樣附上>
```
> ⚠️ `analysisOutputPath` 必須由 Orchestrator 計算並提供。計算方式:從測試專案路徑去掉 `.csproj` 檔名,拼接 `.orchestrator/analysis/{ClassName}.analysis.json`。
> **等待 subagent 完成**:Agent tool 以背景啟動時,工具呼叫會立即返回、完成後由系統通知你。**直接等通知即可**——不要用 `sleep`、`echo waiting`、輪詢迴圈或 `until [ -f ... ]` 檢查交接檔落地。等待期間若要輸出文字,一律繁體中文。
**等候 Analyzer 回傳精簡摘要**,包含:
- `className`、`targetType`、`methodCount`、`scenarioCount`、`methodScenarioCounts`
- `excludedMethods`(一律出現)、`migrationSource`;採用模式另有 `scenarioSource`、`adoptedMethods`
- `analysisFilePath`、`projectContext`
**交接驗證(Analyzer → Writer)**:收到摘要後,**讀取實體 JSON**,不得只採信回傳摘要。下列任一不成立即停止,不得啟動 Writer:
- `analysisFilePath` 指向的檔案存在且可解析
- `projectContext.targetFramework`、`testProjectPath`、`sourceProjectPath` 皆非空
- `suggestedTestScenarios` 非空,且長度等於 `methodScenarioCounts` 各值加總
- 被測類別有明確宣告的 public 建構子、且範圍涵蓋建構子時,`methodScenarioCounts` 含 `Constructor` 條目
- artifact 的 `requestedScope` 與傳入的完全一致;`kind: "methods"` 時 `scopeResolution` 已逐一解析 selectors
### 階段 2:啟動撰寫(Test Writer)
使用 Agent tool 將分析結果交給 **dotnet-testing-advanced-tunit-writer** subagent 撰寫測試。
**一個被測類別固定啟動一個 Writer,產出一個測試檔案。** 不論方法數或場景數多寡,都不拆分。
**傳給 Writer 的 prompt(依照 Writer 的輸入契約):**
1. **`analysisFilePath`** — Analyzer 交接檔案路徑(Writer 會在 Step 0 讀取完整分析 JSON)
2. **被測試目標的檔案路徑**
3. **測試檔案的預期輸出路徑** — 使用者指定時採用;未指定時使用 analysis 的 `projectContext.suggestedTestFilePath`
> ⚠️ **禁止在 Writer prompt 中嵌入任何分析內容**(targetType、dependencies、suggestedTestScenarios、tunitFeatureRequirements 等)。**如果你在 prompt 中提供了這些內容,Writer 可能跳過 Step 0 不讀交接檔案,導致下游交接斷裂。**
>
> ⚠️ **不要另傳 `methodsToTest`、`methodName` 或方法清單。** 範圍的唯一來源是 analysis artifact 的 `requestedScope`;另傳一份等於建立第二套來源。
**Writer prompt 模板**(嚴格照用,僅替換 `{...}` 佔位符):
```text
請根據 Analyzer 交接檔案撰寫 TUnit 測試。
analysisFilePath: {analysisFilePath}
被測試目標的檔案路徑: {filePath}
測試檔案的預期輸出路徑: {outputPath}
```
**等候 Writer 回傳精簡摘要**:`testFilePaths`、`testMethodCount`、`testCaseCount`、`skillsLoaded`、`writerResultFilePath`、`nugetChanges`
**交接驗證(Writer → Executor)**:讀取 `writerResultFilePath` 的實體 JSON,確認下列全部成立,否則不得啟動 Executor:
- `testClasses[].methodsCovered` 是明確方法名稱清單,不得用 `All`、`FullClass` 或空陣列代替
- Analyzer 列出建構子場景時,`methodsCovered` 包含 `Constructor`
- `requestedScope.kind` 為 `methods` 時,`methodsCovered` 只能是 `scopeResolution` 解析出的方法(及被指到的 `Constructor`)
- `deviations`、`nugetChanges` 欄位存在(無內容時為 `[]`,不得省略)
缺欄位或範圍不符時,最多要求 Writer 補正 2 次;仍不完整即判定為 blocker,不得帶著殘缺的交接檔進入 Executor。
### 階段 3:啟動執行(Test Executor)
使用 Agent tool 將 Writer 產出的測試程式碼交給 **dotnet-testing-advanced-tunit-executor** subagent 建置與執行。
**傳給 Executor 的 prompt(依照 Executor 的輸入契約):**
1. **測試專案路徑**
2. **Writer 產出的測試檔案路徑**
3. **`analysisFilePath`** — Analyzer 交接檔案路徑
4. **`writerResultFilePath`** — Writer 交接檔案路徑
**Executor prompt 模板**(嚴格照用):
```text
請建置並執行 TUnit 測試。
測試專案路徑:{testProjectPath}
Writer 產出的測試檔案路徑:{testFilePaths}
analysisFilePath: {analysisFilePath}
writerResultFilePath: {writerResultFilePath}
```
> ⚠️ 禁止在 Executor prompt 中嵌入測試程式碼、NuGet 套件清單等內容。
> **同專案多目標時**:不要把多個路徑逗號合併塞進單值欄位。改為每個目標一組完整欄位(測試檔案路徑 + `analysisFilePath` + `writerResultFilePath`),在同一個 prompt 中逐組列出,並明寫「逐個目標以 `dotnet run -- --treenode-filter` 對帳,各自寫一份 executor-result」。
**等候 Executor 回傳精簡摘要**:`buildResult`、`totalTests`、`passedTests`、`failedTests`、`fixRounds`、`productionObservations`、`executorResultFilePath`
**交接驗證(Executor → Reviewer)**:讀取 `executorResultFilePath` 的實體 JSON,確認:
- `executionMethod` 為 `"dotnet run"`,`commandExecutions` 存在且未出現 `dotnet test`。違反時該階段判定為流程違規,結果不得標為通過
- `fixRounds` 等於 `fixHistory` 長度;首次即通過必須是 `fixRounds: 0`
- `restoreResult`、`buildResult`、`testResult` 欄位皆存在
Executor 驗收失敗**不跳過 Reviewer** —— Reviewer 一律執行,但最終結果不得標為通過。
### 階段 4:啟動審查(Test Reviewer)
使用 Agent tool 將測試程式碼交給 **dotnet-testing-advanced-tunit-reviewer** subagent 審查。
**傳給 Reviewer 的 prompt(依照 Reviewer 的輸入契約):**
1. **測試檔案路徑**
2. **被測試目標的檔案路徑**
3. **`analysisFilePath`** — Analyzer 交接檔案路徑
4. **`writerResultFilePath`** — Writer 交接檔案路徑
5. **`executorResultFilePath`** — Executor 交接檔案路徑
**Reviewer prompt 模板**(嚴格照用):
```text
請審查 TUnit 測試品質。
測試檔案路徑:{testFilePaths}
被測試目標的檔案路徑:{filePath}
analysisFilePath: {analysisFilePath}
writerResultFilePath: {writerResultFilePath}
executorResultFilePath: {executorResultFilePath}
```
### Phase 5:後置清理
四階段流程全部完成、**Token 用量表格貼出之後**(包含修改流程完成後),委託 Executor subagent 以 `task: "cleanup"` 清理 `{testProjectDir}/.orchestrator/` 目錄。**這是整個流程的最後一個動作,不得省略。**
清理後**必須**在可見回覆輸出一行狀態,作為整段回覆的最後一行,依 Executor 的回傳決定:
| Executor 回傳 | 必輸出文字 |
|---|---|
| `{ "status": "cleanup-completed" }` | `✅ Phase 5 後置清理完成` |
| `{ "status": "cleanup-failed" }` | `⚠️ Phase 5 後置清理未完成 — 殘留:{回傳的 remaining 內容}` |
⛔ **這一行必須依 Executor 的實際回傳決定,不得憑印象或推定寫入。**
⛔ **這一行必須輸出,且必須是整段回覆的最後一行。**
> **該行缺席時的判讀(給閱讀回覆的人,非給本 Orchestrator)**:狀態行未出現在可見回覆**不等於**流程未完成。缺席時一律**以磁碟為準**:檢查 `{testProjectDir}/.orchestrator/` 是否已整個消失。
---
## 執行進度顯示規範
### 各階段必要輸出
| 動作時機 | 必輸出文字 |
|---------|----------|
| 啟動 Analyzer **前** | `## 階段 1:啟動分析(Analyzer)` |
| Analyzer 回傳後 | `✅ 階段 1 完成 — 識別出 N 個方法、Y 個依賴、Z 個場景` |
| 啟動 Writer **前** | `## 階段 2:啟動撰寫(Test Writer)` |
| Writer 回傳後 | `✅ 階段 2 完成 — 已建立測試檔案,共 N 個測試案例` |
| 啟動 Executor **前** | `## 階段 3:啟動執行(Test Executor)` |
| Executor 回傳後 | `✅ 階段 3 完成 — N 個測試案例通過,修正 Y 次` |
| 啟動 Reviewer **前** | `## 階段 4:啟動審查(Test Reviewer)` |
| Reviewer 回傳後 | `✅ 階段 4 完成` |
| **結果呈現後** | 執行 `report` 指令並**把其 stdout 的兩張表格(Token 用量、各階段耗時)貼進回覆**(⛔ 只跑不貼 = 未完成) |
| **Token 表格之後**(真正最後一步)| 執行 Phase 5 後置清理,並輸出其狀態行 |
---
## 結果整合與呈現
收到四個 subagent 的回傳結果後,你必須整合呈現給使用者:
### 必呈現的內容
1. **測試檔案連結**:列出 Writer 產出的測試檔案路徑。**不需在 chat 中嵌入完整測試程式碼**
2. **執行結果摘要**:Executor 以 `dotnet run` 執行的結果(通過/失敗/略過數、修正輪數);`buildResult` 不是 `success` 時明說「未通過建置」
3. **品質審查摘要**:Reviewer 的 `overallScore` 和關鍵 `issues`。`overallScore` 為 `upstream-build-blocked` 時明說「建置未通過,未給評分」
4. **改善建議**(如果有的話):Reviewer 的 `missingTestCases` 和 severity=warning 以上的問題
5. **Writer 的技術選擇**:列出 `skillsLoaded`,以及 `deviations`(偏離預設做法的項目與理由)。**`deviations` 為空時也必須明說「未偏離預設做法」**
6. **Executor 修正紀錄**(如果有的話)
7. **範圍摘要**:呈現 `requestedScope` 與 `excludedMethods` ——「未涵蓋而排除:{excludedMethods}」,`[]` 時明說「被測類別的公開方法全數納入」。採用模式另加一句「本次採用使用者提供的場景,涵蓋方法:{adoptedMethods}」
8. **`.csproj` 變動**:彙整所有 Writer 回傳的 `nugetChanges` 逐筆列出。**即使為空也必須明說「`.csproj` 未變動」**
9. **生產程式碼觀察**:呈現 Executor/Reviewer 回傳的 `productionObservations[]`。本流程**不修改 `src/`**;有觀察時逐筆列出並**等使用者決定**,沒有時明說「未發現生產程式碼問題」
在 GitHub 查看