如何在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
相关产品推荐
相关产品推荐

