Agent skill
test-async-guardian
Flutter/Dart 測試異步資源管理守護者。用於:(1) 診斷測試卡住問題,(2) 審查測試程式碼中的異步資源清理,(3) 提供 tearDown 最佳實踐,(4) 掃描潛在的資源洩漏風險。觸發場景:測試卡住、撰寫新測試、Code Review 測試程式碼、執行 flutter test 前自動掃描。
Install this agent skill to your Project
npx add-skill https://github.com/tarrragon/claude/tree/main/skills/test-async-guardian
SKILL.md
Test Async Guardian
測試異步資源管理守護者 - 防止測試因未清理的異步資源而卡住。
問題背景
Dart/Flutter 測試框架會等待所有未完成的 Future 才結束測試。如果測試啟動了長延遲的異步操作但沒有正確清理,測試會無限期卡住。
導致測試阻塞的異步資源類型
| 資源類型 | 問題描述 | 清理方法 |
|---|---|---|
| 未完成的 Future | 長延遲異步操作沒有等待完成或取消 | service.clearAllQueries() 或 service.dispose() |
| Timer.periodic | 週期性定時器未取消 | timer.cancel() |
| StreamController | 廣播流未關閉 | controller.close() |
| Completer | Completer 未完成 | completer.complete() |
| 網路依賴 | flutter test 啟動時需要 pub.dev DNS 解析 | 確保網路連線,或使用離線模式 |
| testWidgets 時間 | Future.delayed 在 widget 測試中無法自動推進 | 呼叫 tester.pump() 或改用 test() |
| MCP run_tests 大量輸出 | 全部測試輸出量過大導致 MCP 卡住 | 使用 paths 參數或改用 flutter test |
正確的 tearDown 模式
模式 A:服務清理
late BookQueryService bookQueryService;
setUp(() {
bookQueryService = BookQueryService(apiService: mockApiService);
});
tearDown(() {
bookQueryService.clearAllQueries(); // 清理未完成的查詢
});
模式 B:Mock 狀態重置
late MockSearchBookViewModelForBatch mockSearchViewModel;
tearDown(() {
mockSearchViewModel.setSlowSearch(false); // 重置慢速模式
mockBookRepository.setSlowQuery(false);
mockBatchEnrichBooksUseCase.setFastExecution(true);
container.dispose();
});
模式 C:StreamController 清理
final StreamController<EnrichmentProgress> _progressController =
StreamController<EnrichmentProgress>.broadcast();
void dispose() {
_progressController.close();
}
模式 D:Timer 清理
Timer? _memoryMonitorTimer;
void startMonitoring() {
_memoryMonitorTimer = Timer.periodic(interval, (_) {
captureSnapshot();
});
}
void dispose() {
_memoryMonitorTimer?.cancel();
_memoryMonitorTimer = null;
}
危險模式檢測
危險模式 1:長延遲沒有清理
// ❌ 危險:10 秒延遲,測試只等 10ms
when(mockApiService.queryByIsbn('xxx'))
.thenAnswer((_) async {
await Future.delayed(const Duration(seconds: 10));
return data;
});
await Future.delayed(const Duration(milliseconds: 10));
// 測試結束,但 Future 還在運行!
修復:添加 tearDown 清理或縮短延遲時間。
危險模式 2:週期性定時器沒有取消
// ❌ 危險:Timer.periodic 未在 tearDown 中取消
Timer.periodic(Duration(seconds: 1), (timer) {
captureSnapshot();
});
修復:保存 Timer 引用並在 dispose/tearDown 中取消。
危險模式 3:StreamController 未關閉
// ❌ 危險:broadcast StreamController 未關閉
final controller = StreamController<Event>.broadcast();
// 測試結束但 controller 仍在監聽
修復:在 dispose 方法中調用 controller.close()。
危險模式 4:網路斷線導致測試卡住
# ❌ 危險:網路斷線時 flutter test 卡在 Resolving dependencies
Resolving dependencies...
ClientException with SocketException: Failed host lookup: 'pub.dev'
症狀:
- 測試卡在 "Resolving dependencies..."
- 錯誤訊息包含 "Failed host lookup: 'pub.dev'"
修復:確認網路連線後重新執行測試。
危險模式 5:testWidgets 中 Future.delayed 永不完成
// ❌ 危險:testWidgets 使用虛擬時鐘,Future.delayed 永不完成
testWidgets('事件測試', (tester) async {
await Future.delayed(Duration(milliseconds: 50)); // 永遠等待!
expect(events.length, 3);
});
症狀:
- 測試卡在特定 testWidgets 測試
- 測試中有 Future.delayed 但沒有 tester.pump()
修復選項:
// 選項 A: 添加 pump(需要 Widget 環境時)
testWidgets('事件測試', (tester) async {
await Future.delayed(Duration(milliseconds: 50));
await tester.pump(Duration(milliseconds: 50)); // 推進虛擬時間
expect(events.length, 3);
});
// 選項 B: 改用 test(推薦,純邏輯測試時)
test('事件測試', () async {
await Future.delayed(Duration(milliseconds: 50)); // 正常執行
expect(events.length, 3);
});
判斷標準:
- 需要 Widget 樹?→ 用 testWidgets + pump
- 只驗證邏輯?→ 用 test
危險模式 6:MCP run_tests 執行全部測試卡住
# ❌ 危險:不指定 paths 執行全部測試會卡住 20+ 分鐘
mcp__dart__run_tests (無 paths 參數)
症狀:
- 使用 MCP run_tests 執行全部測試時卡住超過 20 分鐘
- 但
flutter test直接執行約 85 秒完成 - MCP 工具無回應
根本原因:
- MCP run_tests 是實驗性功能
- 處理大量測試輸出時存在效能問題
- 全部測試輸出量過大導致 MCP 無法處理
修復:
# ✅ 正確 - 必須指定 paths 參數限制測試範圍
mcp__dart__run_tests(roots: [{"root": "file:///path", "paths": ["test/domains/"]}])
mcp__dart__run_tests(roots: [{"root": "file:///path", "paths": ["test/unit/core/"]}])
# ✅ 推薦 - 全量測試使用 Bash(最穩定)
flutter test --reporter compact
./.claude/hooks/test-summary.sh
適用場景對照:
| 測試範圍 | MCP run_tests | flutter test |
|---|---|---|
| 單一檔案 | ✅ 適用 | ✅ 適用 |
| 單一目錄 (paths) | ✅ 適用 | ✅ 適用 |
| 全部測試 | ❌ 禁止 | ✅ 推薦 |
掃描腳本使用
命令行模式
# 掃描單個測試檔案(嚴格模式)
uv run .claude/skills/test-async-guardian/scripts/async_resource_scanner.py \
test/unit/domains/scanner/book_query_service_test.dart
# 掃描整個測試目錄
uv run .claude/skills/test-async-guardian/scripts/async_resource_scanner.py \
test/unit/ --recursive
# 警告模式(不阻止執行)
uv run .claude/skills/test-async-guardian/scripts/async_resource_scanner.py \
test/unit/ --warn-only
Hook 模式
腳本已整合為 PreToolUse Hook,在執行 flutter test 或 dart test 前自動觸發掃描。
診斷流程
當測試卡住時:
-
檢查是否使用 MCP run_tests 執行全部測試
bash# 如果使用 mcp__dart__run_tests 不指定 paths,會卡住 20+ 分鐘 # 解決方案:改用 flutter test 或指定 paths 參數 # ✅ 正確方式 flutter test --reporter compact mcp__dart__run_tests(paths: ["test/domains/"]) -
檢查網路連線
bash# 確認網路連線正常(排除 pub.dev DNS 問題) ping -c 1 pub.dev || echo "網路可能有問題" -
識別卡住的測試
bash# 使用 timeout 限制測試時間(macOS 需要安裝 coreutils) timeout 30 flutter test test/unit/path/to/test.dart # 或使用背景程序方式 flutter test test/unit/path/to/test.dart & sleep 30 && pkill -f flutter_tester -
掃描異步資源問題
bashuv run .claude/skills/test-async-guardian/scripts/async_resource_scanner.py \ test/unit/path/to/test.dart -
檢查報告中的問題
- 長延遲(>= 5 秒)
- 缺少 tearDown
- Timer.periodic 未取消
- StreamController 未關閉
- MCP run_tests 無 paths 參數
-
應用修復建議
Resources
scripts/
async_resource_scanner.py- 異步資源掃描腳本,支援嚴格模式和 Hook 模式
references/
async-cleanup-patterns.md- 清理模式參考和真實案例
hooks/
pre-test-scan.py- PreToolUse Hook 入口腳本
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
skill-design-guide
Use this skill when creating a new skill, updating an existing skill's YAML frontmatter, or reviewing skill quality. Provides the official Anthropic skill specification, frontmatter rules, description writing best practices, progressive disclosure architecture, and common pitfalls to avoid. Triggers include: creating skills, skill review, frontmatter validation, SKILL.md writing.
agent-team
Agent Teams 協作派發指南。Use when: (1) Agent A 的發現會改變 Agent B 正在進行的工作, (2) 用戶要求使用 team/swarm, (3) 多代理人需即時協商共用介面或 API 契約。涵蓋 team 建立、Ticket-Task 橋接、teammate 入職、生命週期管理。
tdd
TDD 全流程指導工具。Use for: (1) 開始新功能的 TDD 流程(Phase 0-4), (2) 推進到下一個 TDD 階段, (3) Phase 1 SOLID 原則驅動功能拆分分析, (4) 查看當前 TDD 進度和階段狀態, (5) 評估是否需要 Phase 4 重構以及 3b 拆分評估。Use when: 開始新功能開發、進入任何 TDD Phase、需要 SOLID 拆分指導、需要確認當前所在 TDD 階段、需要做 Phase 4 豁免判斷時。
branch-worktree-guardian
Branch Worktree Guardian - Git 分支和 Worktree 管理工具。Use for: (1) 新開發需求時建立隔離分支, (2) 使用 worktree 機制避免分支衝突, (3) 驗證當前工作分支正確性, (4) 預防在錯誤分支上開發
design-decision-framework
多方案評估決策框架。用於面臨 3+ 技術方案時的結構化評估、架構決策時的系統化分析,防止衝動決策和技術債務累積。Use for: 技術方案選擇、重大架構決策、高風險技術選型
bulk-evaluate
子任務拆分與 Context 卸載工具。將可拆分的大型任務分成 N 個子 Ticket,各由 Agent 獨立執行,結論直接寫入 Ticket 不回報主線程。Use for: 批量檔案評估, 大型審查任務拆分, 任何需要讀取大量資料但結果可落地到 Ticket 的任務
Didn't find tool you were looking for?