iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
Software Development

Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程系列 第 25

Day25 - Temporal Testing(下):Async Activity Completion、JUnit5 Extension、E2E 測試環境建立

  • 分享至 

  • xImage
  •  

本篇的測試主題為:

  1. 非同步 Activity Completion 測試
  2. 使用 JUnit5 Extension 簡化生命週期管理
  3. 建立 E2E 測試環境的作法

1. 非同步 Activity Completion 測試

在 Activity 內以 doNotCompleteOnReturn() 延後完成,由外部(或背景執行緒)透過 ActivityCompletionClient 回傳結果。

1.1 Activity 及 Workflow 的介面與實作

@ActivityInterface
public interface AsyncAct {
  @ActivityMethod
  String waitExternal(String id);
}
class AsyncActImpl implements AsyncAct {
  private final ActivityCompletionClient completionClient;

  AsyncActImpl(ActivityCompletionClient completionClient) {
    this.completionClient = completionClient;
  }

  @Override
  public String waitExternal(String id) {
    // 標記此 Activity 當前不回傳結果,稍後由外部完成
    ActivityExecutionContext ctx = Activity.getExecutionContext();
    ctx.doNotCompleteOnReturn();
    // 擷取 task token,供外部 completion client 使用
    byte[] token = ctx.getTaskToken();

    // 模擬外部系統 callback:取得 token 後於背景完成 Activity
    new Thread(() -> completionClient.complete(token, "READY-" + id)).start();

    return null;
  }
}
@WorkflowInterface
public interface AsyncWf {
  @WorkflowMethod
  String run(String id);
}
class AsyncWfImpl implements AsyncWf {
  @Override
  public String run(String id) {
    AsyncAct act = Workflow.newActivityStub(
        AsyncAct.class,
        ActivityOptions.newBuilder()
            .setStartToCloseTimeout(Duration.ofSeconds(10))
            .build());
    return act.waitExternal(id);
  }
}

1.2 測試案例

public class AsyncCompletionTest {
  @Test
  void async_activity_completion() {
    try (TestWorkflowEnvironment env = TestWorkflowEnvironment.newInstance()) {
      Worker w = env.newWorker("async-q"); // 測試專用 task queue
      ActivityCompletionClient acc = env.getWorkflowClient().newActivityCompletionClient(); // 供外部完成
      w.registerWorkflowImplementationTypes(AsyncWfImpl.class);
      // 以匿名類別提供 async activity 實作
      w.registerActivitiesImplementations(new AsyncActImpl(acc));

      env.start();

      WorkflowClient c = env.getWorkflowClient();
      AsyncWf wf = c.newWorkflowStub(AsyncWf.class,
          WorkflowOptions.newBuilder().setTaskQueue("async-q").build());

      // 由 activity completion client 回填 "READY-X1"
      String out = wf.run("X1");
      assertEquals("READY-X1", out);
    }
  }
}

1.3 測試重點

  • 外部回填機制:doNotCompleteOnReturn() 後,方法回傳值不會完成 Activity,真正結果由 ActivityCompletionClient.complete(token, value) 回填。
  • token 來源:使用 Activity.getExecutionContext().getTaskToken(),確保一次性、可辨識該 Activity 嘗試。
  • 例外回填:用 completeExceptionally(token, Throwable) 驗證 Workflow 端會收到 ApplicationFailure(或對應例外),避免吞例外。

2. JUnit 5

若偏好以 Extension 管理生命週期,可用 TestWorkflowExtension

2.1 TestWorkflowExtension vs TestWorkflowEnvironment 比較表

面向 TestWorkflowExtension TestWorkflowEnvironment
定位 JUnit 5 Extension 封裝 原生測試環境 API
生命週期 由 JUnit 管理啟停 由測試程式手動啟停
樣板碼 少,builder 註冊即可 多,需註冊/start()/close()
客製化彈性 較少,但可取用底層 env 最高,便於進階組態/多 worker
測試框架綁定 綁 JUnit 5 無(框架無關)
適用場景 一般單元/內嵌整合,追求易用 進階情境、細緻控制、非 JUnit
優點 易用、穩定、少樣板、避免漏關閉 完整掌控、彈性強、可程式化流程
缺點 彈性略低、需 JUnit 5 樣板多、易因順序/關閉疏漏出錯

2.2 程式碼範例

@WorkflowInterface
interface GreetingWorkflow {
  @WorkflowMethod
  String greet(String name);
}
class GreetingWorkflowImpl implements GreetingWorkflow {
  @Override
  public String greet(String name) {
    return "Hello, " + name + "!";
  }
}
  • 測試案例
public class ExtensionStyleTest {

  @RegisterExtension
  public static final TestWorkflowExtension ext = TestWorkflowExtension.newBuilder()
      .registerWorkflowImplementationTypes(GreetingWorkflowImpl.class) // 註冊 workflow 實作
      // 自動使用預設的 task queue
      .setDoNotStart(false) // 測試前自動啟動
      .build();

  // 1. 注入 TestWorkflowEnvironment 與 WorkflowClient,手動建立 stub(彈性高)
  @Test
  void run_with_env_and_client(TestWorkflowEnvironment env, WorkflowClient client) {
    GreetingWorkflow wf = client.newWorkflowStub(
        GreetingWorkflow.class,
        WorkflowOptions.newBuilder().setTaskQueue("default").build()
    );
    String res = wf.greet("Zoe");
    assertEquals("Hello, Zoe!", res);
  }

  // 2. 直接注入 Workflow stub(最省樣板, 自動使用預設的 task queue)
  @Test
  void run_with_injected_stub(GreetingWorkflow wf) {
    String res = wf.greet("Ada");
    assertEquals("Hello, Ada!", res);
  }
}

2.3 測試重點

  • 參數注入:可直接注入 TestWorkflowEnvironmentWorkflowClient,也可直接注入 Workflow Stub,減少樣板程式碼。
  • Task Queue 行為:TestWorkflowExtension 不提供自訂 task queue;注入的 stub 已綁定內部私有 queue。若手動用 WorkflowClient 建 stub,須指定與內部一致的 queue,否則 Worker 收不到任務。
  • 隔離與生命週期:每個測試用獨立環境最穩;若用 static extension 共用環境,請確保狀態清理。

3. E2E 測試環境

啟動 Temporal 有多種方式,基本上程式碼不需要變動,只要搭配慣用的 CI 設定,再導入開發測試流程即可,以下僅列兩種方法。

  1. 如本機測試一樣,安裝好 temporal server, 再簡單啟動測試用的環境 temporal server start-dev
  2. 使用 temporalio/auto-setup 來準備環境,可參考官方的 docker compose 初步的設定

3.1 說明官方的 docker-compose-postgres.yml 以供參考

version: "3.5"
services:
  #postgresql: ...
  temporal:
    container_name: temporal
    depends_on:
      - postgresql
    environment:
      - DB=postgres12
      - DB_PORT=5432
      - POSTGRES_USER=temporal
      - POSTGRES_PWD=temporal
      - POSTGRES_SEEDS=postgresql
      - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
      - TEMPORAL_ADDRESS=temporal:7233
      - TEMPORAL_CLI_ADDRESS=temporal:7233
    image: temporalio/auto-setup:${TEMPORAL_VERSION}
    networks:
      - temporal-network
    ports:
      - 7233:7233 # 啟動之後,可以透過 7233 連上 server
    volumes:
      - ./dynamicconfig:/etc/temporal/config/dynamicconfig
  # temporal-admin-tools: ...
  temporal-ui:
    container_name: temporal-ui
    depends_on:
      - temporal
    environment:
      - TEMPORAL_ADDRESS=temporal:7233
      - TEMPORAL_CORS_ORIGINS=http://localhost:3000
    image: temporalio/ui:${TEMPORAL_UI_VERSION}
    networks:
      - temporal-network
    ports:
      - 8080:8080 # 啟動之後,可以透過 8080 連上 ui
networks:
  temporal-network:
    driver: bridge
    name: temporal-network

結語

最後一篇 Temporal Testing 處理了非同步 Activity Completion 測試、分享搭配 JUnit 5 使用 TestWorkflowExtension 簡化的作法,以及如何建立 E2E 測試環境。


上一篇
Day24 - Temporal Testing(中):Workflow Versioning、Workflow.await、Heartbeat/Cancel
下一篇
Day26 - AI 對話平台:整合 Temporal 與 AI Agent (上)
系列文
Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言