测试与调试
Temporal 提供完善的测试和调试工具,帮助开发者确保工作流的正确性和可靠性。本文档介绍工作流单元测试、Activity Mock、时间模拟以及 Temporal Web UI 和 CLI 的使用。
Workflow 单元测试
Temporal Go SDK 提供 TestWorkflowEnvironment 用于在测试环境中模拟工作流执行。
TestWorkflowEnvironment 基本使用
go
package workflow_test
import (
"testing"
"time"
"go.temporal.io/sdk/testsuite"
"go.temporal.io/sdk/workflow"
"myapp/workflow"
)
func TestOrderWorkflow(t *testing.T) {
// 创建测试套件
testSuite := &testsuite.WorkflowTestSuite{}
// 创建测试环境
env := testSuite.NewTestWorkflowEnvironment()
// 注册工作流
env.RegisterWorkflow(workflow.OrderProcessingWorkflow)
// 注册活动
env.RegisterActivity(workflow.ValidateOrder)
env.RegisterActivity(workflow.ProcessPayment)
env.RegisterActivity(workflow.ProcessOrder)
// 执行工作流
env.ExecuteWorkflow(workflow.OrderProcessingWorkflow, &workflow.Order{
ID: "order-123",
Items: []workflow.Item{{ProductID: "prod-1", Quantity: 2}},
Total: 100.0,
})
// 验证工作流完成
env.AssertExpectations(t)
// 获取工作流结果
var result workflow.OrderResult
err := env.GetWorkflowResult(&result)
if err != nil {
t.Fatalf("Workflow failed: %v", err)
}
// 验证结果
if result.Status != "completed" {
t.Errorf("Expected status 'completed', got '%s'", result.Status)
}
}使用 testify 断言
go
package workflow_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.temporal.io/sdk/testsuite"
"myapp/workflow"
)
func TestOrderWorkflowWithAssert(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.OrderProcessingWorkflow)
env.RegisterActivity(workflow.ValidateOrder)
env.RegisterActivity(workflow.ProcessPayment)
env.ExecuteWorkflow(workflow.OrderProcessingWorkflow, workflow.Order{ID: "test-123"})
// 使用 testify 断言
assert.True(t, env.IsWorkflowCompleted())
var result workflow.OrderResult
require.NoError(t, env.GetWorkflowResult(&result))
assert.Equal(t, "completed", result.Status)
assert.NotEmpty(t, result.OrderID)
}Activity Mock
在测试中可以使用 Mock Activity 替代真实的 Activity 实现,控制返回值和行为。
基本 Mock
go
package workflow_test
import (
"testing"
"go.temporal.io/sdk/testsuite"
"myapp/workflow"
)
func TestWorkflowWithMockActivity(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.PaymentWorkflow)
// Mock ValidateOrder Activity
env.OnActivity(workflow.ValidateOrder, workflow.Order{ID: "test-123"}).Return(nil)
// Mock ProcessPayment Activity - 返回支付 ID
env.OnActivity(workflow.ProcessPayment, workflow.Order{ID: "test-123"}).Return("payment-456", nil)
// Mock ProcessOrder Activity
env.OnActivity(workflow.ProcessOrder, "payment-456").Return(nil)
env.ExecuteWorkflow(workflow.PaymentWorkflow, workflow.Order{ID: "test-123"})
env.AssertExpectations(t)
var result string
err := env.GetWorkflowResult(&result)
require.NoError(t, err)
assert.Equal(t, "payment-456", result)
}Mock 错误返回
go
func TestWorkflowWithMockError(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.PaymentWorkflow)
// Mock 返回错误
env.OnActivity(workflow.ValidateOrder, workflow.Order{ID: "invalid"}).Return(
errors.New("invalid order"),
)
env.ExecuteWorkflow(workflow.PaymentWorkflow, workflow.Order{ID: "invalid"})
// 工作流应该失败
assert.True(t, env.IsWorkflowCompleted())
var result string
err := env.GetWorkflowResult(&result)
// 验证错误
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid order")
}Mock ApplicationError
go
func TestWorkflowWithApplicationError(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.PaymentWorkflow)
// Mock 返回 ApplicationError(不可重试)
env.OnActivity(workflow.ProcessPayment, workflow.Order{ID: "test-123"}).Return(
temporal.NewApplicationError("insufficient balance", "PaymentFailed", true),
)
env.ExecuteWorkflow(workflow.PaymentWorkflow, workflow.Order{ID: "test-123"})
env.AssertExpectations(t)
var result string
err := env.GetWorkflowResult(&result)
assert.Error(t, err)
}Mock 多次调用
go
func TestWorkflowWithMultipleCalls(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.BatchProcessingWorkflow)
// Mock 多次调用
items := []string{"item-1", "item-2", "item-3"}
// 使用 Times 指定调用次数
env.OnActivity(workflow.ProcessItem, "item-1").Return(nil).Times(1)
env.OnActivity(workflow.ProcessItem, "item-2").Return(nil).Times(1)
env.OnActivity(workflow.ProcessItem, "item-3").Return(nil).Times(1)
// 或者使用 Once
env.OnActivity(workflow.ProcessItem, mock.Anything).Return(nil)
env.ExecuteWorkflow(workflow.BatchProcessingWorkflow, items)
env.AssertExpectations(t)
}Mock 动态行为
go
func TestWorkflowWithDynamicMock(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.RetryWorkflow)
// Mock 动态返回值:前两次失败,第三次成功
callCount := 0
env.OnActivity(workflow.UnstableActivity, mock.Anything).Run(
func(args mock.Arguments) {
callCount++
if callCount <= 2 {
// 返回错误
args.Return(errors.New("temporary failure"))
} else {
// 返回成功
args.Return("success", nil)
}
},
)
env.ExecuteWorkflow(workflow.RetryWorkflow, "input")
env.AssertExpectations(t)
// 验证调用次数(包括重试)
assert.Equal(t, 3, callCount)
}时间模拟
测试环境中可以控制时间流逝,测试长时间运行的工作流或定时器行为。
基本时间控制
go
func TestWorkflowTimer(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.TimerWorkflow)
env.RegisterActivity(workflow.DelayedActivity)
// 设置起始时间
env.SetStartTime(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC))
env.ExecuteWorkflow(workflow.TimerWorkflow, 24*time.Hour)
// 工作流启动后立即检查(时间为起始时间)
assert.False(t, env.IsWorkflowCompleted())
// 模拟时间流逝
env.Sleep(24*time.Hour)
// 现在工作流应该完成
assert.True(t, env.IsWorkflowCompleted())
var result string
err := env.GetWorkflowResult(&result)
require.NoError(t, err)
assert.Equal(t, "delayed-activity-completed", result)
}测试超时
go
func TestWorkflowTimeout(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.TimeoutWorkflow)
// Mock 活动不返回(模拟长时间执行)
env.OnActivity(workflow.LongRunningActivity, mock.Anything).Return(nil)
env.ExecuteWorkflow(workflow.TimeoutWorkflow, "input")
// 模拟超过超时时间
env.Sleep(31 * time.Second) // StartToCloseTimeout = 30s
assert.True(t, env.IsWorkflowCompleted())
var result string
err := env.GetWorkflowResult(&result)
// 验证超时错误
var timeoutErr *temporal.TimeoutError
assert.True(t, errors.As(err, &timeoutErr))
}测试定时器取消
go
func TestWorkflowTimerCancellation(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.TimerCancellationWorkflow)
env.RegisterActivity(workflow.ProcessActivity)
env.OnActivity(workflow.ProcessActivity, mock.Anything).Return(nil)
env.ExecuteWorkflow(workflow.TimerCancellationWorkflow, "input")
// 在定时器触发前发送取消信号
env.Sleep(5 * time.Second) // 定时器设置为 10 秒
// 发送取消信号
env.SignalWorkflow("cancel", true)
// 工作流应该因取消而完成
env.Sleep(1 * time.Second) // 让取消处理完成
assert.True(t, env.IsWorkflowCompleted())
var result string
err := env.GetWorkflowResult(&result)
assert.Error(t, err) // 应该返回取消错误
}测试 Cron 工作
go
func TestCronWorkflow(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.ReportWorkflow)
env.RegisterActivity(workflow.GenerateReport)
env.OnActivity(workflow.GenerateReport, mock.Anything).Return("report-1", nil).Once()
env.OnActivity(workflow.GenerateReport, mock.Anything).Return("report-2", nil).Once()
// 执行 Cron 工作流(每 1 小时执行)
env.ExecuteWorkflow(workflow.ReportWorkflow, "daily-report")
// 模拟第一次执行
env.Sleep(1 * time.Hour)
// 模拟第二次执行
env.Sleep(1 * time.Hour)
// 验证活动调用次数
env.AssertExpectations(t)
}Signal 和 Query 测试
测试 Signal 处理
go
func TestWorkflowSignal(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.SignalWorkflow)
env.RegisterActivity(workflow.ProcessApproval)
env.OnActivity(workflow.ProcessApproval, true).Return(nil)
env.ExecuteWorkflow(workflow.SignalWorkflow, "request-123")
// 发送审批信号
env.SignalWorkflow("approval", workflow.ApprovalSignal{
Approved: true,
Approver: "admin",
})
// 等待工作流完成
assert.True(t, env.IsWorkflowCompleted())
var result string
err := env.GetWorkflowResult(&result)
require.NoError(t, err)
}测试多个 Signal
go
func TestWorkflowMultipleSignals(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.BatchSignalWorkflow)
env.RegisterActivity(workflow.ProcessBatch)
env.OnActivity(workflow.ProcessBatch, mock.Anything).Return(nil)
env.ExecuteWorkflow(workflow.BatchSignalWorkflow, "batch-123")
// 发送多个信号
for i := 1; i <= 5; i++ {
env.SignalWorkflow("add-item", fmt.Sprintf("item-%d", i))
env.Sleep(1 * time.Second) // 模拟时间流逝
}
// 发送处理信号
env.SignalWorkflow("process", true)
env.Sleep(1 * time.Second)
assert.True(t, env.IsWorkflowCompleted())
}测试 Query
go
func TestWorkflowQuery(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.QueryableWorkflow)
env.RegisterActivity(workflow.ProcessActivity)
env.OnActivity(workflow.ProcessActivity, mock.Anything).Return(nil)
env.ExecuteWorkflow(workflow.QueryableWorkflow, "task-123")
// 执行查询
result, err := env.QueryWorkflow("get-status", nil)
require.NoError(t, err)
var status string
err = result.Decode(&status)
require.NoError(t, err)
assert.Equal(t, "running", status)
// 模拟时间流逝
env.Sleep(10 * time.Second)
// 再次查询
result, err = env.QueryWorkflow("get-progress", nil)
require.NoError(t, err)
var progress int
err = result.Decode(&progress)
require.NoError(t, err)
assert.Greater(t, progress, 0)
}表驱动测试
使用表驱动测试模式
go
func TestOrderWorkflowTableDriven(t *testing.T) {
tests := []struct {
name string
order workflow.Order
mockSetup func(*testsuite.TestWorkflowEnvironment)
expectedResult workflow.OrderResult
expectError bool
}{
{
name: "successful_order",
order: workflow.Order{ID: "valid-123", Total: 100.0},
mockSetup: func(env *testsuite.TestWorkflowEnvironment) {
env.OnActivity(workflow.ValidateOrder, mock.Anything).Return(nil)
env.OnActivity(workflow.ProcessPayment, mock.Anything).Return("pay-456", nil)
env.OnActivity(workflow.ProcessOrder, mock.Anything).Return(nil)
},
expectedResult: workflow.OrderResult{Status: "completed"},
expectError: false,
},
{
name: "invalid_order",
order: workflow.Order{ID: "invalid", Total: -1.0},
mockSetup: func(env *testsuite.TestWorkflowEnvironment) {
env.OnActivity(workflow.ValidateOrder, mock.Anything).Return(
errors.New("invalid total"),
)
},
expectedResult: workflow.OrderResult{},
expectError: true,
},
{
name: "payment_failed",
order: workflow.Order{ID: "pay-failed", Total: 100.0},
mockSetup: func(env *testsuite.TestWorkflowEnvironment) {
env.OnActivity(workflow.ValidateOrder, mock.Anything).Return(nil)
env.OnActivity(workflow.ProcessPayment, mock.Anything).Return(
temporal.NewApplicationError("insufficient funds", "PaymentFailed", true),
)
},
expectedResult: workflow.OrderResult{},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.OrderProcessingWorkflow)
env.RegisterActivity(workflow.ValidateOrder)
env.RegisterActivity(workflow.ProcessPayment)
env.RegisterActivity(workflow.ProcessOrder)
tt.mockSetup(env)
env.ExecuteWorkflow(workflow.OrderProcessingWorkflow, tt.order)
env.AssertExpectations(t)
var result workflow.OrderResult
err := env.GetWorkflowResult(&result)
if tt.expectError {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expectedResult.Status, result.Status)
}
})
}
}Temporal Web UI
Temporal Web UI 提供可视化界面,用于监控和调试工作流。
Web UI 功能
┌─────────────────────────────────────────────────────────────┐
│ Temporal Web UI │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 工作流列表 │ │
│ │ - 按状态过滤:Running/Completed/Failed/Timed Out │ │
│ │ - 按类型过滤:Workflow Type │ │
│ │ - 按时间范围过滤 │ │
│ │ - 搜索属性 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 工作流详情 │ │
│ │ - Workflow ID / Run ID │ │
│ │ - 状态和执行时间 │ │
│ │ - 输入和输出 │ │
│ │ - 搜索属性 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 事件历史 │ │
│ │ - 事件类型和 ID │ │
│ │ - 时间戳 │ │
│ │ - 详细信息 │ │
│ │ - 可视化图表 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 操作功能 │ │
│ │ - Cancel Workflow │ │
│ │ - Terminate Workflow │ │
│ │ - Signal Workflow │ │
│ │ - Query Workflow │ │
│ │ - Retry Activity │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘访问 Web UI
bash
# 启动 Temporal 开发服务器时,Web UI 自动启动
temporal server start-dev
# 默认访问地址
# Server: localhost:7233
# Web UI: http://localhost:8233Web UI 使用指南
1. 查看工作流列表
Web UI Dashboard:
- 显示所有命名空间
- 工作流统计信息
- 最近执行的工作流2. 搜索工作流
sql
# 使用搜索查询
WorkflowType = "OrderWorkflow" AND Status = "Running"
# 按时间范围
CloseTime >= "2024-01-01" AND CloseTime < "2024-01-02"
# 按搜索属性
CustomKeyword = "customer-123"3. 查看工作流详情
点击工作流 ID 进入详情页面:
- 基本信息
- Workflow ID
- Run ID
- Workflow Type
- Status
- Start Time
- Close Time
- 输入输出
- 输入参数(JSON)
- 输出结果(JSON)
- 搜索属性
- 自定义搜索属性
- Memo
- 工作流备注4. 查看事件历史
事件历史显示完整执行轨迹:
- 事件 ID
- 事件类型
- 时间戳
- 详细内容
示例:
EventID: 1 WorkflowExecutionStarted
EventID: 2 ActivityTaskScheduled (ActivityID: 1, Type: ValidateOrder)
EventID: 3 ActivityTaskStarted (ActivityID: 1)
EventID: 4 ActivityTaskCompleted (ActivityID: 1, Result: {...})
EventID: 5 TimerStarted (TimerID: 1)
EventID: 6 TimerFired (TimerID: 1)
...5. 执行操作
操作按钮:
- Cancel:取消工作流(等待活动完成)
- Terminate:强制终止工作流
- Signal:发送信号到工作流
- Query:查询工作流状态
- Retry Activity:重试失败的活动CLI 调试命令
Temporal CLI 提供强大的命令行工具用于调试。
基本命令
bash
# 查看命名空间列表
temporal namespace list
# 创建命名空间
temporal namespace create my-namespace --retention 7d
# 查看工作流列表
temporal workflow list
# 按条件过滤
temporal workflow list --query 'WorkflowType="OrderWorkflow" AND Status="Running"'
# 查看工作流详情
temporal workflow describe --workflow-id order-123
# 查看工作流历史
temporal workflow show --workflow-id order-123
# 查看事件历史(详细)
temporal workflow show --workflow-id order-123 --follow工作流操作
bash
# 启动工作流
temporal workflow start \
--type OrderWorkflow \
--task-queue my-queue \
--workflow-id test-123 \
--input '{"orderID":"123"}'
# 取消工作流
temporal workflow cancel --workflow-id order-123
# 终止工作流
temporal workflow terminate --workflow-id order-123 --reason "测试终止"
# 发送信号
temporal workflow signal \
--workflow-id order-123 \
--name payment-received \
--input '{"amount":100}'
# 查询工作流
temporal workflow query \
--workflow-id order-123 \
--name get-statusActivity 操作
bash
# 查看任务队列信息
temporal task-queue describe --task-queue my-queue
# 重试失败的活动
temporal activity complete \
--workflow-id order-123 \
--activity-id 2 \
--result '{"status":"success"}'
# 取消活动
temporal activity fail \
--workflow-id order-123 \
--activity-id 2 \
--reason "手动取消"调试命令
bash
# 查看搜索属性
temporal workflow describe --workflow-id order-123 --json | jq '.searchAttributes'
# 查看 Memo
temporal workflow describe --workflow-id order-123 --json | jq '.memo'
# 查看事件历史(JSON 格式)
temporal workflow show --workflow-id order-123 --json
# 统计工作流数量
temporal workflow list --query 'WorkflowType="OrderWorkflow"' --json | jq 'length'
# 查看 Worker 信息
temporal task-queue describe --task-queue my-queue --json完整测试示例
测试完整工作流
go
package workflow_test
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/testsuite"
"myapp/workflow"
)
func TestFullOrderWorkflow(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.OrderProcessingWorkflow)
env.RegisterActivity(workflow.ValidateOrder)
env.RegisterActivity(workflow.ProcessPayment)
env.RegisterActivity(workflow.ProcessOrder)
env.RegisterActivity(workflow.ShipOrder)
env.RegisterActivity(workflow.SendNotification)
order := workflow.Order{
ID: "order-123",
CustomerID: "customer-456",
Items: []workflow.Item{
{ProductID: "prod-1", Quantity: 2, Price: 50.0},
},
Total: 100.0,
}
// Mock 各步骤
env.OnActivity(workflow.ValidateOrder, order).Return(nil).Once()
env.OnActivity(workflow.ProcessPayment, mock.Anything).Return("payment-789", nil).Once()
env.OnActivity(workflow.ProcessOrder, mock.Anything).Return(nil).Once()
env.OnActivity(workflow.ShipOrder, mock.Anything).Return("tracking-ABC123", nil).Once()
env.OnActivity(workflow.SendNotification, mock.Anything).Return(nil).Once()
// 执行工作流
env.ExecuteWorkflow(workflow.OrderProcessingWorkflow, order)
// 验证 Mock 调用
env.AssertExpectations(t)
// 验证结果
var result workflow.OrderResult
err := env.GetWorkflowResult(&result)
require.NoError(t, err)
assert.Equal(t, "completed", result.Status)
assert.Equal(t, "payment-789", result.PaymentID)
assert.Equal(t, "tracking-ABC123", result.TrackingNumber)
}测试 Saga 补偿
go
func TestSagaCompensation(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.OrderSagaWorkflow)
env.RegisterActivity(workflow.CreateOrder)
env.RegisterActivity(workflow.ReserveInventory)
env.RegisterActivity(workflow.ProcessPayment)
env.RegisterActivity(workflow.ShipOrder)
env.RegisterActivity(workflow.ReleaseInventory)
env.RegisterActivity(workflow.CancelOrder)
env.RegisterActivity(workflow.RefundPayment)
order := workflow.Order{ID: "order-123"}
// Mock 正常步骤
env.OnActivity(workflow.CreateOrder, order).Return("order-123", nil)
env.OnActivity(workflow.ReserveInventory, "order-123").Return(nil)
// Mock 支付失败
env.OnActivity(workflow.ProcessPayment, "order-123").Return(
temporal.NewApplicationError("payment failed", "PaymentFailed", true),
)
// Mock 补偿步骤
env.OnActivity(workflow.ReleaseInventory, "order-123").Return(nil)
env.OnActivity(workflow.CancelOrder, "order-123").Return(nil)
env.ExecuteWorkflow(workflow.OrderSagaWorkflow, order)
env.AssertExpectations(t)
// 工作流应该失败
var result workflow.OrderResult
err := env.GetWorkflowResult(&result)
assert.Error(t, err)
}测试重试策略
go
func TestRetryPolicy(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.RetryWorkflow)
env.RegisterActivity(workflow.UnstableActivity)
// Mock 多次失败后成功
callCount := 0
env.OnActivity(workflow.UnstableActivity, mock.Anything).Run(
func(args mock.Arguments) {
callCount++
if callCount < 3 {
args.Return(errors.New("temporary failure"))
} else {
args.Return("success", nil)
}
},
)
env.ExecuteWorkflow(workflow.RetryWorkflow, "input")
env.AssertExpectations(t)
// 验证重试次数
assert.Equal(t, 3, callCount)
var result string
err := env.GetWorkflowResult(&result)
require.NoError(t, err)
assert.Equal(t, "success", result)
}测试并行执行
go
func TestParallelExecution(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.ParallelWorkflow)
env.RegisterActivity(workflow.ProcessItem)
items := []string{"item-1", "item-2", "item-3"}
// Mock 并行处理
env.OnActivity(workflow.ProcessItem, "item-1").Return(nil)
env.OnActivity(workflow.ProcessItem, "item-2").Return(nil)
env.OnActivity(workflow.ProcessItem, "item-3").Return(nil)
env.ExecuteWorkflow(workflow.ParallelWorkflow, items)
// 验证所有活动被调用
env.AssertExpectations(t)
// 验证结果
var result int
err := env.GetWorkflowResult(&result)
require.NoError(t, err)
assert.Equal(t, 3, result) // 处理了 3 个项目
}测试长时间运行工作流
go
func TestLongRunningWorkflow(t *testing.T) {
testSuite := &testsuite.WorkflowTestSuite{}
env := testSuite.NewTestWorkflowEnvironment()
env.RegisterWorkflow(workflow.LongRunningWorkflow)
env.RegisterActivity(workflow.ProcessBatch)
// Mock 批处理
env.OnActivity(workflow.ProcessBatch, mock.Anything).Return(nil)
env.ExecuteWorkflow(workflow.LongRunningWorkflow, "task-123")
// 模拟时间流逝(1 小时)
env.Sleep(1 * time.Hour)
// 检查进度查询
result, err := env.QueryWorkflow("get-progress", nil)
require.NoError(t, err)
var progress int
err = result.Decode(&progress)
require.NoError(t, err)
assert.GreaterOrEqual(t, progress, 10)
// 继续模拟时间(到完成)
env.Sleep(23 * time.Hour)
assert.True(t, env.IsWorkflowCompleted())
}调试技巧
1. 使用日志调试
go
func DebugWorkflow(ctx workflow.Context, input string) error {
logger := workflow.GetLogger(ctx)
// 记录关键步骤
logger.Info("Workflow started", "input", input)
ao := workflow.ActivityOptions{
StartToCloseTimeout: time.Minute,
}
ctx = workflow.WithActivityOptions(ctx, ao)
var result string
err := workflow.ExecuteActivity(ctx, SomeActivity, input).Get(ctx, &result)
if err != nil {
logger.Error("Activity failed", "error", err, "input", input)
return err
}
logger.Info("Activity completed", "result", result)
// 记录中间状态
logger.Info("Processing result", "status", "in-progress")
return nil
}2. 使用 Query 查看状态
go
func QueryableWorkflow(ctx workflow.Context, taskID string) error {
var state struct {
TaskID string
Status string
Progress int
StartTime time.Time
Errors []string
}
state.TaskID = taskID
state.StartTime = workflow.Now(ctx)
state.Status = "running"
// 设置 Query Handler
workflow.SetQueryHandler(ctx, "get-state", func() (interface{}, error) {
return state, nil
})
// 工作流逻辑中更新状态
state.Progress = 10
ao := workflow.ActivityOptions{
StartToCloseTimeout: 5 * time.Minute,
}
ctx = workflow.WithActivityOptions(ctx, ao)
err := workflow.ExecuteActivity(ctx, SomeActivity, taskID).Get(ctx, nil)
if err != nil {
state.Errors = append(state.Errors, err.Error())
state.Status = "error"
return err
}
state.Progress = 100
state.Status = "completed"
return nil
}3. 使用 CLI 查看历史
bash
# 查看工作流历史,调试问题
temporal workflow show --workflow-id problem-123
# JSON 格式便于分析
temporal workflow show --workflow-id problem-123 --json | jq '.history.events[] | {id, type, timestamp}'
# 查看特定事件类型
temporal workflow show --workflow-id problem-123 --json | jq '.history.events[] | select(.eventType == "ActivityTaskFailed")'4. 使用 Web UI 可视化
Web UI 提供可视化视图:
- 工作流执行图
- 时间线视图
- 活动依赖关系
- 错误点标记最佳实践
测试最佳实践
go
// 1. 使用表驱动测试
func TestWorkflowTableDriven(t *testing.T) {
tests := []struct {
name string
input string
mock func(*testsuite.TestWorkflowEnvironment)
expected string
wantErr bool
}{
// 多个测试场景...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 执行测试...
})
}
}
// 2. Mock 所有外部依赖
func TestIsolatedWorkflow(t *testing.T) {
env := testSuite.NewTestWorkflowEnvironment()
// Mock 所有 Activity
env.OnActivity(ActivityA, mock.Anything).Return(nil)
env.OnActivity(ActivityB, mock.Anything).Return(nil)
env.OnActivity(ExternalAPI, mock.Anything).Return("mocked-response", nil)
// 不依赖真实外部服务
}
// 3. 测试错误场景
func TestErrorScenarios(t *testing.T) {
tests := []struct {
name string
mock func(*testsuite.TestWorkflowEnvironment)
wantErr string
}{
{
name: "validation_error",
mock: func(env *testsuite.TestWorkflowEnvironment) {
env.OnActivity(Validate, mock.Anything).Return(errors.New("invalid"))
},
wantErr: "invalid",
},
{
name: "payment_timeout",
mock: func(env *testsuite.TestWorkflowEnvironment) {
env.OnActivity(Payment, mock.Anything).Return(
temporal.NewTimeoutError("timeout", temporal.TimeoutTypeStartToClose),
)
},
wantErr: "timeout",
},
}
}
// 4. 测试边界情况
func TestBoundaryCases(t *testing.T) {
// 空输入
// 最大输入
// 超时边界
// 并发上限
}