本篇重點:使用 Temporal Testing SDK 跑起一個 Temporal 服務模擬,並了解如何開始測試 Worker、Workflow 與 Activity。
Activity
。TestWorkflowEnvironment
跑 in‑memory service/worker/client。io.temporal.testing.TestWorkflowEnvironment
:Temporal 提供了一個測試框架,用於簡化 Workflow 單元測試和整合測試。該測試框架提供了一個 TestWorkflowEnvironment 類,其中包含 Temporal 服務的記憶體實作,支援快轉跳過時間,輕鬆測試長時間運行的 Workflow。io.temporal.testing.TestActivityEnvironment
:用來獨立測試 Activity
。plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'io.temporal:temporal-sdk:1.31.0'
// 測試套件
testImplementation 'io.temporal:temporal-testing:1.31.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3'
}
test {
useJUnitPlatform()
}
@WorkflowInterface
public interface GreetingWorkflow {
// Workflow 入口方法:必須是 deterministic,且可被重播
@WorkflowMethod
String greet(String name);
// Signal:在執行中由外部傳入事件以更新 workflow 狀態
@SignalMethod
void updateGreeting(String greeting);
// Query:讀取 workflow 目前狀態,不改變狀態
@QueryMethod
String getGreeting();
}
@ActivityInterface
public interface GreetingActivities {
// Activity:可含 I/O 與非決定性操作
@ActivityMethod
String composeGreeting(String greeting, String name);
}
public class GreetingWorkflowImpl implements GreetingWorkflow {
private String currentGreeting = "Hello";
// 以固定 timeout 建立 Activity stub;由 Workflow 執行循環呼叫
private final GreetingActivities activities =
Workflow.newActivityStub(
GreetingActivities.class,
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofSeconds(5))
.build());
@Override
public String greet(String name) {
// *** 測試時將快轉跨越這段 sleep ***
Workflow.sleep(Duration.ofHours(1));
return activities.composeGreeting(currentGreeting, name);
}
@Override
public void updateGreeting(String greeting) {
// Signal handler:更新 workflow 內部狀態
this.currentGreeting = greeting;
}
@Override
public String getGreeting() {
// Query handler:回報目前狀態
return currentGreeting;
}
}
public class GreetingActivitiesImpl implements GreetingActivities {
@Override
public String composeGreeting(String greeting, String name) {
return String.format("%s, %s!", greeting, name);
}
}
TestWorkflowEnvironment
啟動 Worker
與 Workflow/Activity,搭配時間快轉驗證互動public class GreetingWorkflowTest {
private static final String TASK_QUEUE = "greeting-task-queue";
// in-memory Temporal service 與 client
private TestWorkflowEnvironment testEnv;
private WorkflowClient client;
@BeforeEach
void setUp() {
// 建立 in-memory Temporal service 與 client
testEnv = TestWorkflowEnvironment.newInstance();
// 建立 worker 並註冊 workflow / activities 實作
Worker worker = testEnv.newWorker(TASK_QUEUE);
worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class);
// 以匿名類別提供 Activity 實作,避免額外測試替身類別
worker.registerActivitiesImplementations(new GreetingActivities() {
@Override
public String composeGreeting(String greeting, String name) {
return String.format("%s, %s!", greeting, name);
}
});
// 啟動內嵌服務
testEnv.start();
client = testEnv.getWorkflowClient();
}
@AfterEach
void tearDown() {
testEnv.close();
}
@Test
void greet_withVirtualTime_andSignal() {
GreetingWorkflow workflow = client.newWorkflowStub(
GreetingWorkflow.class,
WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build());
// 非同步啟動 workflow,讓我們能在進行中送 Signal
WorkflowClient.start(() -> workflow.greet("Alice"));
// *** greet 內有 Workflow.sleep 會被快轉跳過 ***
// 立刻用 Signal 更新狀態
workflow.updateGreeting("Howdy");
// 取得結果並驗證
String result = WorkflowStub.fromTyped(workflow).getResult(String.class);
assertEquals("Howdy, Alice!", result);
}
}
WorkflowClient.start
非同步啟動後即可在送 Signal
或做 Query
。Workflow.sleep(Duration.ofHours(1))
是快轉通過不會卡時間的。當 Activity
需取用 ActivityExecutionContext.getInfo()
資訊(例如 workflowId),就需要用到 SDK 來進行驗證。
public class GreetingActivitySdkTest {
private static final String TASK_QUEUE = "greeting-task-queue";
private TestWorkflowEnvironment testEnv;
private WorkflowClient client;
@BeforeEach
void setUp() {
testEnv = TestWorkflowEnvironment.newInstance();
Worker worker = testEnv.newWorker(TASK_QUEUE);
worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class);
// Activity 實作
worker.registerActivitiesImplementations(new GreetingActivities() {
@Override
public String composeGreeting(String greeting, String name) {
// 讀取 workflowId 並組合回傳字串
String wfId = Activity.getExecutionContext().getInfo().getWorkflowId();
return String.format("%s, %s! (%s)", greeting, name, wfId);
}
});
testEnv.start();
client = testEnv.getWorkflowClient();
}
@AfterEach
void tearDown() {
testEnv.close();
}
@Test
void activity_can_access_workflowId_via_context() {
String expectedWorkflowId = "wf-ctx-1";
GreetingWorkflow workflow = client.newWorkflowStub(
GreetingWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue(TASK_QUEUE)
.setWorkflowId(expectedWorkflowId)
.build());
// 非同步啟動(自動快轉跳過 Workflow.sleep 計時器)
WorkflowClient.start(() -> workflow.greet("Bob"));
// 驗證 Activity 的回傳包含 workflowId(透過 SDK context 取得)
String result = WorkflowStub.fromTyped(workflow).getResult(String.class);
assertEquals("Hello, Bob! (" + expectedWorkflowId + ")", result);
}
}
我們可以透過 Temporal 測試框架用最小成本覆蓋 Workflow 與 Activity 的核心行為,快速得到穩定且可重現的測試回饋。