Skip to content

测试与调试 ​

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:8233

Web 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-status

Activity 操作 ​

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) {
    // 空输入
    // 最大输入
    // 超时边界
    // 并发上限
}

相关资源 ​