关于 testing-flow
使用 `@robomotion/sdk/testing` 和 `bun test` 编写并运行 Robomotion 流程行为测试的技能,涵盖流程检查、服务模拟、Function 节点隔离测试和子流程测试。
主要功能
- 01使用 `bun test` 运行 `*.test.ts`
- 02检查流程结构和执行路径
- 03模拟外部服务并跟踪调用
- 04隔离测试 Function 节点代码
- 05独立运行子流程
- 06切换单元与集成模式
适用场景
- 01运行 Robomotion 流程行为测试
- 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— 生成 TypeScriptrunning-flow— 在机器人上执行
评论
支持 Markdown