Skip to content
Embabel 文档版本 0.3.0-SNAPSHOT 源快照 083e038e 更新于 2026-09-29

39 · 测试(Testing)

和 Spring 一样,Embabel 让用户应用的测试变得容易,为单元测试与集成测试都提供了完善支持。

构建生成式 AI 应用与构建其它软件并无不同。 测试是交付高质量软件的关键,且必须从一开始就纳入考虑。

单元测试(Unit Testing)

Embabel 的单元测试让你在不涉及真实 LLM 调用的前提下测试单个 Agent 动作。

Embabel 的设计意味着 Agent 通常就是 POJO,可以用 fake 或 mock 对象实例化; 动作就是普通方法,可以直接用测试夹具(fixture)调用。 除了你的领域对象,你还要传入一个 OperationContext 的测试夹具,从而能够拦截并验证 LLM 调用。

框架提供 FakePromptRunner 与 FakeOperationContext,用于 mock LLM 交互, 同时允许你校验提示词、超参数与业务逻辑。当然你也可以直接用 mock 对象 —— Java 侧默认用 Mockito,Kotlin 侧用 mockk。

测试提示词与超参数

下面是 Java Agent Template 与 Kotlin Agent Template 仓库中的单元测试,使用 Embabel 的 fake 对象:

java
class WriteAndReviewAgentTest {

    @Test
    void testWriteAndReviewAgent() {
        var context = FakeOperationContext.create();
        var promptRunner = (FakePromptRunner) context.promptRunner();
        context.expectResponse(new Story("One upon a time Sir Galahad . . "));

        var agent = new WriteAndReviewAgent(200, 400);
        agent.craftStory(new UserInput("Tell me a story about a brave knight", Instant.now()), context);

        String prompt = promptRunner.getLlmInvocations().getFirst().getPrompt();
        assertTrue(prompt.contains("knight"), "Expected prompt to contain 'knight'");

        var temp = promptRunner.getLlmInvocations().getFirst().getInteraction().getLlm().getTemperature();
        assertEquals(0.9, temp, 0.01,
                "Expected temperature to be 0.9: Higher for more creative output");
    }

    @Test
    void testReview() {
        var agent = new WriteAndReviewAgent(200, 400);
        var userInput = new UserInput("Tell me a story about a brave knight", Instant.now());
        var story = new Story("Once upon a time, Sir Galahad...");
        var context = FakeOperationContext.create();
        context.expectResponse("A thrilling tale of bravery and adventure!");
        agent.reviewStory(userInput, story, context);

        var promptRunner = (FakePromptRunner) context.promptRunner();
        String prompt = promptRunner.getLlmInvocations().getFirst().getPrompt();
        assertTrue(prompt.contains("knight"), "Expected review prompt to contain 'knight'");
        assertTrue(prompt.contains("review"), "Expected review prompt to contain 'review'");
    }
}

测试流式 API:withId() 与 creating()

FakePromptRunner 完整支持生产代码里使用的流式 API 模式, 因此可以对"用 withId() 做交互追踪、用 creating() 做带样例的结构化对象创建"的 Agent 做充分单测。

测试 withId() 的交互追踪:

withId() 方法为更好的日志追踪设置交互 ID。测试中你可以验证交互 ID 被正确设置:

java
@Test
void shouldSetInteractionIdCorrectly() {
    var context = FakeOperationContext.create();
    var expectedIntent = new UserIntent("command", "Change channel names");
    context.expectResponse(expectedIntent);

    var result = context.ai()
            .withId("classify-intent") // 设置交互 ID 以便追踪
            .creating(UserIntent.class)
            .fromPrompt("Classify the user's intent");

    assertEquals(expectedIntent, result);

    // 验证交互 ID 被正确设置
    var interaction = context.getLlmInvocations().getFirst().getInteraction();
    assertEquals("classify-intent", interaction.getId().getValue());
}

测试 creating() 搭配 withExample():

creating() API 允许你提供强类型样例来提升 LLM 输出质量。测试中可以验证样例确实被带上:

java
@Test
void shouldIncludeExamplesInPrompt() {
    var context = FakeOperationContext.create();
    var expectedPlan = new ChannelEditPlan(1, "Lead Vox");
    context.expectResponse(expectedPlan);

    var result = context.ai()
            .withLlm(llmSelectionService.selectOptimalLlm())
            .withId("analyze-edit-request")
            .creating(ChannelEditPlan.class)
            .withExample("Rename channel 1", new ChannelEditPlan(1, "Bass"))
            .withExample("Rename channel 2", new ChannelEditPlan(2, "Drums"))
            .fromPrompt("Analyze the edit request");

    assertEquals(expectedPlan, result);

    // 验证样例被作为 prompt contributor 加入
    var promptContributors = context.getLlmInvocations().getFirst()
            .getInteraction().getPromptContributors();
    assertTrue(promptContributors.size() >= 2, "Examples should be added as prompt contributors");
}

用 CreationExample 做可复用样例:

为了代码更干净、更可复用,你可以用 CreationExample 数据类定义样例,在测试之间共享,或作为集合传入:

java
@Test
void shouldUseCreationExampleDataClass() {
    var context = FakeOperationContext.create();
    var expectedPlan = new ChannelEditPlan(1, "Lead Vox");
    context.expectResponse(expectedPlan);

    // 用 CreationExample 创建一个可复用样例
    var example = new CreationExample<>(
            "Rename channel example",
            new ChannelEditPlan(2, "Rhythm")
    );

    var result = context.ai()
            .withDefaultLlm()
            .creating(ChannelEditPlan.class)
            .withExample(example) // 直接传入 CreationExample
            .fromPrompt("Analyze the edit request");

    assertEquals(expectedPlan, result);
}

用 withExamples() 一次加多个样例:

样例很多时,用 withExamples() 以列表或可变参数传入。 这在样例从文件或数据库加载时尤其有用:

java
@Test
void shouldAddMultipleExamplesFromList() {
    var context = FakeOperationContext.create();
    var expectedPlan = new ChannelEditPlan(1, "Lead Vox");
    context.expectResponse(expectedPlan);

    // 创建样例列表(可以是从配置里加载的)
    var examples = List.of(
            new CreationExample<>("Rename to Bass", new ChannelEditPlan(1, "Bass")),
            new CreationExample<>("Rename to Drums", new ChannelEditPlan(2, "Drums")),
            new CreationExample<>("Rename to Keys", new ChannelEditPlan(3, "Keys")),
            new CreationExample<>("Rename to Vocals", new ChannelEditPlan(4, "Vocals"))
    );

    var result = context.ai()
            .withDefaultLlm()
            .creating(ChannelEditPlan.class)
            .withExamples(examples) // 一次传入所有样例
            .fromPrompt("Analyze the request");

    assertEquals(expectedPlan, result);

    // 验证所有样例都被加入
    var promptContributors = context.getLlmInvocations().getFirst()
            .getInteraction().getPromptContributors();
    assertTrue(promptContributors.size() >= 4);
}

也可以用可变参数语法内联写样例列表:

java
var result = context.ai()
        .withDefaultLlm()
        .creating(ChannelEditPlan.class)
        .withExamples(
                new CreationExample<>("Example 1", new ChannelEditPlan(1, "Bass")),
                new CreationExample<>("Example 2", new ChannelEditPlan(2, "Drums")),
                new CreationExample<>("Example 3", new ChannelEditPlan(3, "Keys"))
        )
        .fromPrompt("Analyze the request");

完整的流式 API 链式示例:

下面是一个完整例子,展示如何测试一个用上全部流式 API 特性的动作:

java
@Test
void shouldTestCompleteFluentApiChain() {
    var context = FakeOperationContext.create();
    var expectedOutput = new ComplexOutput("analysis complete", 42);
    context.expectResponse(expectedOutput);

    // 生产代码里完整流式 API 链的写法
    var result = context.ai()
            .withLlm(LlmOptions.withModel("gpt-4"))
            .withId("complex-analysis")
            .withSystemPrompt("You are an expert analyst")
            .creating(ComplexOutput.class)
            .withExample("Simple case", new ComplexOutput("basic", 1))
            .withExample("Complex case", new ComplexOutput("advanced", 100))
            .fromPrompt("Analyze the input data");

    assertEquals(expectedOutput, result);

    // 全面校验
    var invocation = context.getLlmInvocations().getFirst();
    assertEquals("gpt-4", invocation.getInteraction().getLlm().getModel());
    assertEquals("complex-analysis", invocation.getInteraction().getId().getValue());
    assertTrue(invocation.getInteraction().getPromptContributors().size() >= 3); // system + 2 个样例
}

演示出来的关键测试模式

测试提示词内容:

  • 用 context.getLlmInvocations().getFirst().getPrompt() 取到实际发给 LLM 的提示词;
  • 用 assertTrue(prompt.contains(...)) 校验关键领域数据确实被写进了提示词。

测试工具(Tools):

  • 通过 getInteraction().getTools() 访问工具,校验 withToolObject() / withTool() 加进来的工具;
  • 通过 getInteraction().getToolGroups() 访问工具组需求,校验 withToolGroup(ToolGroupRequirement) 加进来的具名工具组需求。

getTools() 返回的是真实的 Tool 实例(来自 withToolObject() 与 withTool()), 而 getToolGroups() 返回的是 ToolGroupRequirement 对象 (如通过 withToolGroup() 加的具名需求 "web_search")。多数测试应当用 getTools()。

测试带 Spring 依赖的组件:

  • 用标准 mock 框架 mock 诸如 HoroscopeService 这类 Spring 注入的服务;
  • 把 mock 后的依赖传给 Agent 构造器,实现隔离的单元测试。

测试多次 LLM 交互

java
@Test
void shouldHandleMultipleLlmInteractions() {
    // Arrange
    var input = new UserInput("Write about space exploration");
    var story = new Story("The astronaut gazed at Earth...");
    ReviewedStory review = new ReviewedStory("Compelling narrative with vivid imagery.");

    // 按顺序设置期望响应
    context.expectResponse(story);
    context.expectResponse(review);

    // Act
    var writtenStory = agent.writeStory(input, context);
    ReviewedStory reviewedStory = agent.reviewStory(writtenStory, context);

    // Assert
    assertEquals(story, writtenStory);
    assertEquals(review, reviewedStory);

    // 验证两次 LLM 调用都发生了
    List<LlmInvocation> invocations = context.getLlmInvocations();
    assertEquals(2, invocations.size());

    // 验证第一次调用(写作者)
    var writerCall = invocations.get(0);
    assertEquals(0.8, writerCall.getInteraction().getLlm().getTemperature(), 0.01);

    // 验证第二次调用(评审者)
    var reviewerCall = invocations.get(1);
    assertEquals(0.2, reviewerCall.getInteraction().getLlm().getTemperature(), 0.01);
}

你也可以直接用 Mockito 或 mockk。考虑这个组件 —— 它直接注入 Ai:

java
@Component
public record InjectedComponent(Ai ai) {

    public record Joke(String leadup, String punchline) {
    }

    public String tellJokeAbout(String topic) {
        return ai
                .withDefaultLlm()
                .generateText("Tell me a joke about " + topic);
    }
}

用 Mockito(Java)或 mockk(Kotlin)写单元测试,校验提示词与超参数:

java
class InjectedComponentTest {

    @Test
    void testTellJokeAbout() {
        var mockAi = Mockito.mock(Ai.class);
        var mockPromptRunner = Mockito.mock(PromptRunner.class);

        var prompt = "Tell me a joke about frogs";
        // 是的,这个笑话是一个 LLM 想出来的。
        var terribleJoke = """
                Why don't frogs ever pay for drinks?
                Because they always have a tadpole in their wallet!
                """;
        when(mockAi.withDefaultLlm()).thenReturn(mockPromptRunner);
        when(mockPromptRunner.generateText(prompt)).thenReturn(terribleJoke);

        var injectedComponent = new InjectedComponent(mockAi);
        var joke = injectedComponent.tellJokeAbout("frogs");

        assertEquals(terribleJoke, joke);
        Mockito.verify(mockAi).withDefaultLlm();
        Mockito.verify(mockPromptRunner).generateText(prompt);
    }

}

集成测试(Integration Testing)

集成测试演练完整的 Agent 工作流,使用真实或 mock 的外部服务, 但为了可预测性与速度,仍然避免真实的 LLM 调用。这可以确保:

  • Agent 被 Agent 平台正确拾取;
  • Agent 内部数据流正确;
  • 失败场景被优雅处理;
  • Agent 之间、Agent 与外部系统之间正确交互;
  • 整体工作流行为符合预期;
  • LLM 提示词与超参数配置正确。

Embabel 的集成测试构建在 Spring 优秀的集成测试支持之上, 因此你想用真实数据库也可以。Spring 与 Testcontainers 的集成尤其有用。

使用 EmbabelMockitoIntegrationTest

Embabel 提供 EmbabelMockitoIntegrationTest 作为基类,用便捷的辅助方法简化集成测试:

java
/**
 * Use framework superclass to test the complete workflow of writing and reviewing a story.
 * This will run under Spring Boot against an AgentPlatform instance
 * that has loaded all our agents.
 */
class StoryWriterIntegrationTest extends EmbabelMockitoIntegrationTest {

    @Test
    void shouldExecuteCompleteWorkflow() {
        var input = new UserInput("Write about artificial intelligence");

        var story = new Story("AI will transform our world...");
        var reviewedStory = new ReviewedStory(story, "Excellent exploration of AI themes.", Personas.REVIEWER);

        whenCreateObject(contains("Craft a short story"), Story.class)
                .thenReturn(story);

        // 第二次调用用的是 generateText
        whenGenerateText(contains("You will be given a short story to review"))
                .thenReturn(reviewedStory.review());

        var invocation = AgentInvocation.create(agentPlatform, ReviewedStory.class);
        var reviewedStoryResult = invocation.invoke(input);

        assertNotNull(reviewedStoryResult);
        assertTrue(reviewedStoryResult.getContent().contains(story.text()),
                "Expected story content to be present: " + reviewedStoryResult.getContent());
        assertEquals(reviewedStory, reviewedStoryResult,
                "Expected review to match: " + reviewedStoryResult);

        verifyCreateObjectMatching(prompt -> prompt.contains("Craft a short story"), Story.class,
                llm -> llm.getLlm().getTemperature() == 0.9 && llm.getToolGroups().isEmpty());
        verifyGenerateTextMatching(prompt -> prompt.contains("You will be given a short story to review"));
        verifyNoMoreInteractions();
    }
}

关键集成测试特性

基类带来的好处:

  • EmbabelMockitoIntegrationTest 自动处理 Spring Boot 环境搭建与 LLM mock;
  • 预先配置好 agentPlatform 与 llmOperations;
  • 内置常用测试模式的辅助方法。

便捷的 stub 方法:

  • whenCreateObject(prompt, outputClass) —— mock 对象创建调用;
  • whenGenerateText(prompt) —— mock 文本生成调用;
  • 同时支持精确匹配与 contains() 匹配;
  • 在测试 setup 中调用 supportsStreaming(true) 即可支持流式调用。

进阶校验:

  • verifyCreateObjectMatching() —— 用自定义 matcher 校验提示词;
  • verifyGenerateTextMatching() —— 校验文本生成调用;
  • verifyNoMoreInteractions() —— 确保没有意外的 LLM 调用。

LLM 配置测试:

  • 校验 temperature:llm.getLlm().getTemperature() == 0.9;
  • 检查工具组:llm.getToolGroups().isEmpty();
  • 校验 persona 与其它 LLM 选项。

带真实 LLM 的集成测试

Embabel 还提供一批用真实 LLM 提供方演练完整工作流的集成测试,以验证端到端功能。 这些测试位于各 LLM 提供方的 autoconfiguration 模块中, 验证与提供方 API 的集成是否正确工作,包括护栏、thinking 响应、结构化输出等特性。

LLM 集成测试示例:

测试名说明LLM 提供方
LLMOpenAiGuardRailsIntegrationIT测试 OpenAI 与护栏的集成,含基于 OpenAI SDK 与 Spring AI 两种方式的 moderation API 集成,并验证结构化输出时护栏是否被调用OpenAI (gpt-4.1-mini)
LLMAnthropicGuardRailsIntegrationIT测试 Anthropic 与护栏的集成,验证结构化对象响应时 AssistantMessageGuardRail 被正确调用Anthropic (claude-sonnet-4-5)
LLMGeminiGuardRailsIntegrationIT测试 Gemini 与护栏的集成,验证 Google Gemini 模型在结构化输出下的护栏功能Gemini (gemini-2.5-flash)

测试结构

这些集成测试遵循一致的结构,既测试真实 LLM 交互,又保持对测试环境的控制:

1. 测试位置

集成测试放在 autoconfiguration 模块内,而不是单独的测试模块中。 这样的位置让测试能直接访问它所测试的 autoconfiguration 类, 并验证 Spring Boot 是否正确初始化了所有必需的 bean。

embabel-agent-autoconfigure/
  models/
    embabel-agent-openai-autoconfigure/
      src/test/java/com/embabel/agent/config/models/openai/
        LLMOpenAiGuardRailsIntegrationIT.java
    embabel-agent-anthropic-autoconfigure/
      src/test/java/com/embabel/agent/config/models/anthropic/
        LLMAnthropicGuardRailsIntegrationIT.java
    embabel-agent-gemini-autoconfigure/
      src/test/java/com/embabel/agent/config/models/gemini/
        LLMGeminiGuardRailsIntegrationIT.java

2. 环境属性

每个测试通过 @SpringBootTest 注解的 properties 属性配置 Spring 环境。 这些属性为测试环境配置 LLM 模型、超时、重试与日志级别。

java
@SpringBootTest(
        properties = {
                "embabel.models.cheapest=gpt-4.1-mini",
                "embabel.models.best=gpt-4.1-mini",
                "embabel.models.default-llm=gpt-4.1-mini", // ①
                "embabel.agent.platform.llm-operations.prompts.defaultTimeout=240s",
                "embabel.agent.platform.llm-operations.data-binding.fixedBackoffMillis=6000",

                // Thinking 基础设施日志
                "logging.level.com.embabel.agent.spi.support.springai.ChatClientLlmOperations=TRACE",
                "logging.level.com.embabel.common.core.thinking=DEBUG"

        }
)

① default-llm 属性配置调用 ai.withDefaultLlm() 时默认使用哪个 LLM 模型。

3. 激活的 profile

@ActiveProfiles 注解激活为测试环境启用特定功能的 Spring profile。 "thinking" profile 启用扩展思考能力与 LLM 操作的额外日志。

java
@ActiveProfiles("thinking") // ①

① 激活 "thinking" profile,启用增强追踪与 thinking 块提取。

4. 配置属性扫描

@ConfigurationPropertiesScan 注解让 Spring Boot 能够发现并绑定配置属性类。 这对于集成测试从 embabel.agent 与 embabel.example 包加载所有配置属性至关重要。

java
@ConfigurationPropertiesScan(
        basePackages = {
                "com.embabel.agent",
                "com.embabel.example"
        }
)

5. 组件扫描

@ComponentScan 注解配置组件扫描以发现 Spring bean。 集成测试通常会排除某些可能干扰测试执行的组件(例如异常处理器)。

java
@ComponentScan(
        basePackages = {
                "com.embabel.agent",
                "com.embabel.example"
        }
)

6. 导入 autoconfiguration

@Import 注解显式导入正在被测试的 autoconfiguration 类。 这确保特定 LLM 提供方的 autoconfiguration 被加载,并创建所有必需的 bean。

java
@Import({AgentOpenAiAutoConfiguration.class, GuardRailConfiguration.class}) // ①

① 同时导入 OpenAI autoconfiguration 与测试专用的护栏配置。

7. 自动装配依赖

集成测试自动装配测试所需的 bean。 虽然测试可能为了校验而装配多个组件,但执行 LLM 操作只需要 Ai 接口。

java
@Autowired
private Ai ai; // ①

@Autowired
private Autonomy autonomy; // ②

@Autowired
private List<LlmService<?>> llms; // ②

① Ai 接口是主要入口,也是发起 LLM 调用唯一必需的依赖。 ② 为校验与测试目的而装配的额外组件。

示例测试:OpenAI 护栏集成

下面的例子展示 LLMOpenAiGuardRailsIntegrationIT.java 中的一个完整集成测试:

java
@Test
void testGuardRailInvokedForStructuredCreateObject() {
    logger.info("Starting guardrail structured createObject test"); // ①

    List<String> guardRailCalled = Collections.synchronizedList(new ArrayList<>()); // ②

    AssistantMessageGuardRail trackingGuard = new AssistantMessageGuardRail() { // ③
        @Override
        public @NotNull String getName() {
            return "StructuredOutputTrackingGuardRail";
        }

        @Override
        public @NotNull String getDescription() {
            return "Tracks guardrail invocation for structured output";
        }

        @Override
        public @NotNull ValidationResult validate(@NotNull String input, @NotNull Blackboard blackboard) {
            guardRailCalled.add(input);
            logger.info("AssistantMessageGuardRail invoked for structured output: {}", input);
            return new ValidationResult(true, Collections.emptyList());
        }

        @Override
        public @NotNull ValidationResult validate(@NotNull ThinkingResponse<?> response, @NotNull Blackboard blackboard) {
            return new ValidationResult(true, Collections.emptyList());
        }
    };

    PromptRunner runner = ai.withDefaultLlm() // ④
            .withGuardRails(trackingGuard);

    String prompt = """
            What is the hottest month in Florida and provide its temperature.
            The name should be the month name, temperature should be in Fahrenheit.
            """;

    MonthItem result = runner.createObject(prompt, MonthItem.class); // ⑤

    assertNotNull(result, "Result should not be null"); // ⑥
    assertNotNull(result.getName(), "Month name should not be null");
    assertFalse(guardRailCalled.isEmpty(),
            "AssistantMessageGuardRail should have been called for structured output");
    logger.info("GuardRail was invoked {} time(s) for structured createObject", guardRailCalled.size());
}

① 追踪测试执行的日志语句 ② 线程安全的列表,用于追踪护栏被调用的情况 ③ 自定义护栏实现,记录它何时被调用 ④ 用默认 LLM(在测试属性中配置为 gpt-4.1-mini)配置 prompt runner ⑤ 执行 LLM 调用以创建结构化对象 ⑥ 校验结果与护栏调用

该测试用 withDefaultLlm(),它引用的是 @SpringBootTest 属性中配置的模型 (embabel.models.default-llm=gpt-4.1-mini)。 这种做法让测试更灵活 —— 换个模型不需要改测试代码。

测试注解式 Agent(Annotated Agents)

测试用 @Agent 与 @Action 注解构建的 Agent 时,你需要验证:

  • Agent 元数据由注解正确构造;
  • 动作按正确行为执行(重试策略、前置条件等);
  • 规划器恰当地选择并执行动作;
  • 领域特定逻辑符合预期。

注解式 Agent 的测试结构

测试一个注解式 Agent 的关键步骤:

  • 创建你的注解式 Agent 类实例;
  • 用 AgentMetadataReader 从注解中提取 Agent 元数据;
  • 用假的或真实的 AgentPlatform 创建 AgentProcess;
  • 提供输入数据并运行该进程;
  • 校验输出与任何副作用。

示例:测试动作重试策略

下面这个简洁例子来自 RetryActionAnnotationJavaTest,展示如何测试带重试行为的 Agent:

java
@Test
void retryMethodFailsOnlyOnceSucceedsSecond() {
    // 1. 创建 Agent 实例
    var instance = new JavaAgentWithTwoRetryActions(); // ①

    // 2. 从注解中提取 Agent 元数据
    var reader = new AgentMetadataReader();
    var agent = reader.createAgentMetadata(instance); // ②

    // 3. 用测试平台创建 agent process
    var ap = IntegrationTestUtils.dummyAgentPlatform();
    var agentProcess = ap.createAgentProcess(
            agent,
            ProcessOptions.DEFAULT.withPlannerType(PlannerType.UTILITY), // ③
            Map.of("input", new JavaRetryTestInput("test")) // ④
    );

    // 4. 运行并校验
    assertDoesNotThrow(() ->
            agentProcess.run().resultOfType(JavaRetryTestOutput.class)); // ⑤

    assertEquals(2, instance.retryInvocations.get(),
            "Retryable method should have been invoked twice"); // ⑥
}

① 用所需依赖实例化注解式 Agent ② 用 AgentMetadataReader 解析注解并创建 Agent 元数据 ③ 为测试配置规划器类型(UTILITY、GOAP 等) ④ 以 Map 提供输入数据 —— key 与动作参数名一致 ⑤ 运行进程并按期望类型提取结果 ⑥ 用实例字段(例如调用计数器)校验行为

被测的注解式 Agent:

java
@Agent(description = "Java agent with retry", planner = PlannerType.GOAP)
class JavaAgentWithTwoRetryActions {

    final AtomicInteger retryInvocations = new AtomicInteger(0); // ①

    @AchievesGoal(description = "Process the input")
    @Action(actionRetryPolicyExpression = "${retry-twice}") // ②
    public JavaRetryTestOutput firstAction(JavaRetryTestInput input) {
        retryInvocations.incrementAndGet();
        if (retryInvocations.get() == 1)
            throw new RuntimeException("Failed!"); // ③

        return new JavaRetryTestOutput("Success!"); // ④
    }
}

① 用实例字段追踪调用次数以校验行为 ② 通过注解配置动作行为(重试策略、前置条件等) ③ 第一次调用故意失败,以测试重试行为 ④ 第二次调用成功

关键测试模式:

  • Dummy AgentPlatform:用 IntegrationTestUtils.dummyAgentPlatform() 做不启 Spring 上下文的轻量测试;
  • 实例状态:直接访问实例字段来校验内部行为(调用次数、状态变化);
  • 输入 Map:以 Map 提供动作参数,key 与参数名一致;
  • 结果提取:用 agentProcess.run().resultOfType(ExpectedType.class) 拿到强类型结果;
  • 异常测试:用 assertThrows() 校验失败场景与重试耗尽。

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。