You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Cadence单元测试中用RegisterDelayedCallback执行同工作流?

Cadence单元测试异步活动跨实例完成问题排查

首先明确:Cadence完全支持同一个工作流的多次独立执行,只要每次执行使用不同的工作流ID(或合理配置重复ID规则),每个实例都是隔离的,不会互相干扰。

你的测试出现挂起超时、无活动日志的问题,核心是缺少以下关键逻辑:

1. 跨工作流实例的回调令牌共享

单元测试环境(TestWorkflowEnvironment)的工作流实例内存是完全隔离的,第一次执行时保存在实例内存里的回调令牌,第二次启动的实例根本无法获取。必须用测试环境提供的共享测试数据存储来传递令牌:

  • 第一次执行时:通过workflow.GetTestData(ctx).Set("callbackToken", token)将令牌存入共享存储
  • 第二次执行时:通过workflow.GetTestData(ctx).Get("callbackToken")取出令牌

2. 异步活动的正确配置

要拿到回调令牌,启动活动时必须显式开启异步完成选项:

ao := workflow.ActivityOptions{
    ScheduleToCloseTimeout: time.Hour,
    AsyncCompletionOption: &workflow.AsyncCompletionOption{Enabled: true}, // 必须开启
}
ctx = workflow.WithActivityOptions(ctx, ao)

同时在活动中返回activity.GetInfo(ctx).CallbackToken,这样工作流才能拿到后续用来完成活动的令牌。

3. 测试时钟的主动推进

RegisterDelayedCallback依赖测试环境的虚拟时钟,不会自动推进。你需要手动调用testEnv.AdvanceTime(duration)来触发延迟回调,同时推进时钟让活动和工作流的逻辑得以执行。如果不推进时钟,延迟回调永远不会触发,第二次工作流不会启动,第一次工作流会一直等待活动完成最终超时。

4. 日志输出的开启

测试环境默认可能关闭了详细日志,需要在初始化时开启日志组件,才能看到活动执行的日志:

testEnv.SetLogger(zap.NewExample())

5. 工作流ID的唯一性

两次执行工作流必须使用不同的工作流ID,否则Cadence会认为是同一个实例的重试或恢复,而不是启动新实例。如果非要用相同ID,需要配置StartWorkflowOptions.AllowDuplicate,但不推荐这种做法,容易引发状态混乱。

关键代码示例补充

工作流逻辑(workflow.go)

func MyWorkflow(ctx workflow.Context) error {
    // 配置异步活动选项
    ao := workflow.ActivityOptions{
        ScheduleToCloseTimeout: time.Hour,
        AsyncCompletionOption: &workflow.AsyncCompletionOption{Enabled: true},
    }
    ctx = workflow.WithActivityOptions(ctx, ao)

    // 启动异步活动并获取回调令牌
    var callbackToken string
    if err := workflow.ExecuteActivity(ctx, GetCallbackTokenActivity).Get(ctx, &callbackToken); err != nil {
        return err
    }

    testData := workflow.GetTestData(ctx)
    info := workflow.GetInfo(ctx)

    // 第一次执行:保存令牌到共享存储并等待活动完成
    if info.WorkflowID == "workflow-first" {
        testData.Set("async-token", callbackToken)
        workflow.Await(ctx, func() bool {
            return testData.Has("activity-completed")
        })
    } else if info.WorkflowID == "workflow-second" {
        // 第二次执行:取出令牌并完成活动
        token := testData.Get("async-token").(string)
        if err := workflow.CompleteActivity(ctx, token, nil, nil); err != nil {
            return err
        }
        testData.Set("activity-completed", true)
    }

    return nil
}

// GetCallbackTokenActivity 返回活动的回调令牌
func GetCallbackTokenActivity(ctx context.Context) (string, error) {
    return activity.GetInfo(ctx).CallbackToken, nil
}

测试逻辑(workflow_test.go)

func TestCrossInstanceAsyncCompletion(t *testing.T) {
    testEnv := testsuite.NewTestWorkflowEnvironment()
    // 开启日志便于排查
    testEnv.SetLogger(zap.NewExample())

    // 启动第一次工作流
    testEnv.ExecuteWorkflow("workflow-first", MyWorkflow)
    // 推进时钟让活动执行并返回令牌
    testEnv.AdvanceTime(time.Second)

    // 注册延迟回调启动第二次工作流
    testEnv.RegisterDelayedCallback(func() {
        testEnv.ExecuteWorkflow("workflow-second", MyWorkflow)
        // 推进时钟让第二次工作流完成活动回调
        testEnv.AdvanceTime(time.Second)
    }, time.Second*2)

    // 推进时钟触发延迟回调,并让第一次工作流完成等待
    testEnv.AdvanceTime(time.Second*3)

    // 验证工作流执行结果
    if !testEnv.IsWorkflowCompleted() {
        t.Fatal("第一次工作流未正常完成")
    }
    if err := testEnv.GetWorkflowError(); err != nil {
        t.Fatalf("工作流执行失败: %v", err)
    }
}

内容的提问来源于stack exchange,提问作者3tbraden

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 06:35:03