mcpField
T
testing-flow

testing-flow

github.com/robomotionio测试与质量5120 次安装3 天前更新
代码仓库

关于 testing-flow

使用 `@robomotion/sdk/testing` 和 `bun test` 编写并运行 Robomotion 流程行为测试的技能,涵盖流程检查、服务模拟、Function 节点隔离测试和子流程测试。

主要功能

  1. 01使用 `bun test` 运行 `*.test.ts`
  2. 02检查流程结构和执行路径
  3. 03模拟外部服务并跟踪调用
  4. 04隔离测试 Function 节点代码
  5. 05独立运行子流程
  6. 06切换单元与集成模式

适用场景

  1. 01运行 Robomotion 流程行为测试
  2. 02模拟服务以验证节点行为
  3. 03不运行完整流程而检查 Function 节点逻辑

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/robomotionio/agent-skills --skill testing-flow

安装后在 Claude Code 中输入/testing-flow确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

config.json
reference/
flow-tester.md
function-extraction.md
integration-mode.md
mock-registry.md
subflow-tester.md

详细介绍

测试 Robomotion 流程

为 Robomotion 流程运行行为测试。使用 @robomotion/sdk/testing + bun test。

验证 pspec 架构时,请使用 validating-flow。

用途

  • 使用 bun test 执行 *.test.ts 文件
  • 使用 FlowTester、MockRegistry、SubflowTester 编写新测试
  • 检查结构、模拟服务、跟踪执行、测试 Function 节点逻辑

工作流

  1. 检查测试 — ls path/to/flow/*.test.ts
  2. 运行 — cd path/to/flow && bun test
  3. 修复失败项 — 诊断、编辑并重新运行

快速入门

在 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 节点中的 JavaScriptextractFunc、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