testing-flow について
`@robomotion/sdk/testing` と `bun test` で Robomotion フローの動作テストを作成・実行するスキルです。フローの検査、サービスのモック、Function ノードとサブフローの個別テストに対応します。
主な機能
- 01`bun test` による `*.test.ts` の実行
- 02フロー構造と実行パスの検査
- 03外部サービスのモックと呼び出し追跡
- 04Function ノードコードの個別テスト
- 05サブフローの単独実行
- 06ユニット/統合モードの切り替え
活用シーン
- 01Robomotion フローの動作テスト実行
- 02サービスをモックしてノード動作を検証
- 03フロー全体を実行せず Function ノードを確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ robomotionio/ agent-skills --skill testing-flowインストール後、Claude Code で/testing-flowを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
config.jsonreference/flow-tester.mdfunction-extraction.mdintegration-mode.mdmock-registry.mdsubflow-tester.md詳細
Robomotion フローのテスト
Robomotion フローの動作テストを実行します。@robomotion/sdk/testing と bun test を使用します。
pspec スキーマの検証には、validating-flow を使用してください。
目的
bun testで*.test.tsファイルを実行する- FlowTester、MockRegistry、SubflowTester を使って新しいテストを作成する
- 構造を調べ、サービスをモックし、実行をトレースし、Function ノードのロジックをテストする
ワークフロー
- テストを確認する —
ls path/to/flow/*.test.ts - 実行する —
cd path/to/flow && bun test - 失敗を修正する — 原因を調べ、編集し、再実行する
クイックスタート
main.ts の隣に main.test.ts を作成します:
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { FlowTester, MockRegistry } from '@robomotion/sdk/testing';
describe('My Flow', () => {
let tester: FlowTester;
beforeAll(async () => {
tester = await FlowTester.load('./main.ts');
});
afterAll(() => {
MockRegistry.clear();
});
test('validates', () => {
expect(tester.validate().valid).toBe(true);
});
test('has expected node count', () => {
expect(tester.getNodes().length).toBe(10);
});
});
API マップ
必要なリファレンスファイルだけを読み込んでください。それぞれ単独で完結しており、このスキルの ./reference/ 内にあります。
| 領域 | 内容 | リファレンス |
|---|---|---|
| フローの読み込み・調査、チェックポイントの実行、パスと複雑さの分析 | FlowTester、analyzeFlow、findPaths、generateDotGraph | ./reference/flow-tester.md |
| 外部サービスのモック(ノードごと、タイプごと、ヘルパー、呼び出し追跡) | MockRegistry、mockResponse/Error/Sequence/Conditional | ./reference/mock-registry.md |
| Function ノードの JavaScript を分離してテストする | extractFunc、runFunc、testFunctionWithCases、analyzeFunctionNode | ./reference/function-extraction.md |
| サブフローを分離して実行し、フロー内で検出する | SubflowTester、discoverSubflows、loadAllSubflows | ./reference/subflow-tester.md |
| 環境変数によるユニットテストモードと実サービスモードの切り替え | isIntegrationMode、hasCredentials、createTestHelper | ./reference/integration-mode.md |
完全な例
import { describe, test, expect, beforeAll, afterAll, afterEach } from 'bun:test';
import {
FlowTester, MockRegistry, extractFunc,
isIntegrationMode, hasCredentials, analyzeFlow,
} from '@robomotion/sdk/testing';
describe('Reddit to WordPress Flow', () => {
let tester: FlowTester;
beforeAll(async () => { tester = await FlowTester.load('./main.ts'); });
afterEach(() => MockRegistry.resetCalls());
afterAll(() => MockRegistry.clear());
describe('Structure', () => {
test('validates', () => expect(tester.validate().valid).toBe(true));
test('all branches terminate', () => expect(tester.allBranchesTerminate()).toBe(true));
test('loops have Goto->Label', () => {
expect(tester.findLoops().every(l => l.hasGotoLabel)).toBe(true);
});
test('no dead ends', () => {
expect(analyzeFlow(tester).deadEnds).toHaveLength(0);
});
});
describe('Transform Logic', () => {
test('formats Reddit post', () => {
const transform = extractFunc(tester, '2bf967');
const out = transform({ post: { title: 'Test', selftext: 'Body', subreddit: 'programming' } });
expect(out.wpTitle).toContain('Test');
});
});
describe('With Mocked Services', () => {
beforeAll(() => {
MockRegistry.registerNode('c8f412', async () => ({
posts: [{ title: 'Mock 1', selftext: 'c1', score: 100 }],
}));
MockRegistry.register('Core.Net.HttpRequest', async (p, m, nodeId) => {
console.warn(`Unmocked HTTP in ${nodeId}`);
return { statusCode: 200, body: {} };
});
});
test('fetches posts', async () => {
const res = await tester.runUntil('c5d901', {
input: { subreddit: 'programming', limit: 10 },
});
expect(res.msg.posts).toHaveLength(1);
expect(MockRegistry.getCallCount('c8f412')).toBe(1);
});
});
describe('Integration', () => {
test('real WordPress posting', async () => {
if (!isIntegrationMode() || !hasCredentials('wordpress')) return;
const res = await tester.runWithTrace({
input: { subreddit: 'programming', postLimit: 1 },
useRealServices: true,
});
expect(res.output.publishedUrl).toBeDefined();
});
});
});
ヒント
- 正確に制御するにはノード ID のモックを使用し、すべてに適用するモックにはタイプのモックを使用します。
getLastCall()でモックへの入力を確認します。- ファイル間の汚染を防ぐため、
afterAll()でモックをクリアします。 INTEGRATION=1 bun testで実サービスに切り替わります。
関連スキル
validating-flow— スキーマの検証(プロパティ名、ポート、ノードタイプ)creating-flow— TypeScript の生成running-flow— ロボット上で実行
コメント
Markdown 対応