首页 / Spring AI 入门教程 / 测试与 Testcontainers

Spring AI 入门教程

测试与 Testcontainers

本教程共 45 篇 · 第 40 篇 · 更新于 2026-08-16 · 约 8 分钟阅读

Spring AI测试TestcontainersDocker Compose集成测试OllamaContainer开发期服务单元测试

本节目标:搞懂 AI 应用的测试分层,学会用 mock 做单元测试,用 Development-time Services 和 Testcontainers 起真实依赖做集成测试。

40.1 AI 应用测试的难点

普通应用测试,输入输出都是确定的。AI 应用不一样:模型输出不确定,依赖外部服务,每次调用还花钱。

三个难点对应三种应对:

  • 不确定 → 固定模型参数,用评估器断言质量
  • 外部依赖 → 本地容器起真实服务
  • 成本 → 用 mock 和本地小模型

40.2 测试分层

单元测试。 测自己的逻辑,模型用 mock 替换。比如提示词组装、工具方法、输出解析。

集成测试。 起真实依赖,比如向量库、本地模型,测完整链路。Testcontainers 是主流方案。

评估测试。 用第 38 章的评估器断言回答质量,挂进 CI 做回归。

三层分工不同:单元测试毫秒级,覆盖逻辑分支;集成测试秒级到分钟级,覆盖真实链路;评估测试最慢也最贵,但回答质量只有它能把关。测试金字塔在 AI 应用里一样适用,越往上越少、越精。

层与层之间不是替代关系。mock 测逻辑,容器测链路,评估器测质量,各管一段。少一层,问题就会漏到更晚才被发现。

40.3 单元测试:mock 掉模型

mock 的本质,是把”模型会不会”换成”模型一定答什么”。测试关注的是自己的代码在模型给固定答案时,行为是否正确。

模型调用是 IO,不该出现在单元测试里。用 Mockito mock 一个 ChatModel,返回固定内容:

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.messages.AssistantMessage;
import static org.mockito.Mockito.*;

@Test
void testPromptRendering() {
    ChatModel chatModel = mock(ChatModel.class);
    when(chatModel.call(any(Prompt.class)))
        .thenReturn(fixedResponse("固定回答"));

    ChatClient chatClient = ChatClient.builder(chatModel).build();
    String answer = chatClient.prompt("你好").call().content();

    assertThat(answer).isEqualTo("固定回答");
}

private ChatResponse fixedResponse(String text) {
    return ChatResponse.builder()
        .generations(List.of(new Generation(new AssistantMessage(text))))
        .build();
}
Note

ChatResponse.builder().generations(...) 的写法是示意代码,以 2.0.0 实际 API 为准。

这样测试只关心自己的代码逻辑,不依赖网络和模型。速度快,也不花钱。

Tip

构造固定 ChatResponse 有点啰嗦,可以抽成测试工具方法,全项目共用。

Mockito 还能验证交互。比如断言模型收到的提示词:

ArgumentCaptor<Prompt> captor = ArgumentCaptor.forClass(Prompt.class);
verify(chatModel).call(captor.capture());
assertThat(captor.getValue().getContents()).contains("系统提示词");

这类断言适合验证提示词模板是否生效、参数是否传对。

什么时候必须 mock?提示词模板改动、工具参数解析、输出格式转换,这些逻辑不碰模型,全用 mock 测。什么时候别 mock?要验证模型行为本身时,mock 没有意义,得上真实模型。

40.4 Development-time Services:开发期的 Docker Compose

开发时不想连远程模型服务,用 Docker Compose 在本地起一套。Spring AI 提供自动配置,识别容器后自动注入连接。

加依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-spring-boot-docker-compose</artifactId>
</dependency>

写 compose.yaml:

services:
  ollama:
    image: ollama/ollama
    ports:
      - "11434:11434"
  qdrant:
    image: qdrant/qdrant
    ports:
      - "6333:6333"

应用启动时,模块按容器镜像名匹配,自动配置连接。Ollama 容器映射成 OllamaConnectionDetails,Qdrant 容器映射成 QdrantConnectionDetails,相关 Bean 直接可用。

Spring Boot 的 Docker Compose 支持会在应用启动时自动执行 compose 文件,不用手动敲 docker compose up。默认只负责启动,不负责关闭。需要随应用退出时,把 spring.docker.compose.lifecycle-management(Spring Boot 属性)设为 start-and-stop

支持的连接(节选):

连接详情匹配容器
OllamaConnectionDetailsollama/ollama
ChromaConnectionDetailschromadb/chroma
QdrantConnectionDetailsqdrant/qdrant
WeaviateConnectionDetailssemitechnologies/weaviate
MilvusServiceClientConnectionDetailsmilvusdb/milvus
McpSseClientConnectionDetailsdocker/mcp-gateway
Tip

这套机制来自 Spring Boot 的 Docker Compose 支持。Spring AI 只补充了模型服务和向量库的连接工厂。

为什么叫 Development-time Services?因为它只服务开发期,生产环境不用它。生产部署还是直连真实服务,用配置覆盖连接地址就行。开发、测试、生产三套环境,连接方式不同,业务代码一行不用改。

它还有个隐藏好处:新同事 clone 代码后,一条命令就能把依赖全拉起来,不用折腾本地安装。

40.5 Testcontainers:集成测试的真实依赖

测试环境用 Testcontainers,每个测试起容器、测完销毁,干净隔离。Spring AI 提供同样的自动配置:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-spring-boot-testcontainers</artifactId>
</dependency>

跑集成测试前,机器上要有 Docker。Windows 上装 Docker Desktop,并开启 WSL2 集成。CI 里用带 Docker 的 runner,比如 GitHub Actions 的 ubuntu-latest。

Testcontainers 的版本由 Spring Boot 统一管理,不用手动指定。镜像 tag 自己选,测试环境用固定 tag,别追 latest,否则镜像一更新测试就飘。

用 OllamaContainer 测问答

这里用的是 Testcontainers 标准用法:@Testcontainers 标注测试类,@Container 静态字段声明容器,execInContainer 在容器里执行命令。

import org.testcontainers.ollama.OllamaContainer;

@SpringBootTest
@Testcontainers
class ChatIntegrationTest {

    @Container
    static OllamaContainer ollama = new OllamaContainer("ollama/ollama");

    @BeforeAll
    static void pullModel() throws Exception {
        ollama.execInContainer("ollama", "pull", "qwen2.5:0.5b");
    }

    @Autowired
    ChatClient chatClient;

    @Test
    void testChat() {
        String answer = chatClient.prompt("1+1 等于几?").call().content();
        assertThat(answer).isNotBlank();
    }
}

容器启动后,自动配置识别 OllamaContainer,注入 OllamaConnectionDetails,ChatClient 直接可用。

Note

Ollama 镜像不带模型,测试启动后要先拉模型。首次拉镜像、拉模型都慢,CI 里建议做缓存预热。

向量库 + 问答的完整链路

RAG 测试要把向量库容器和模型容器一起起:

import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.testcontainers.chromadb.ChromaDBContainer;
import org.testcontainers.ollama.OllamaContainer;

@SpringBootTest
@Testcontainers
class RagIntegrationTest {

    @Container
    static OllamaContainer ollama = new OllamaContainer("ollama/ollama");

    @Container
    static ChromaDBContainer chroma = new ChromaDBContainer("chromadb/chroma");

    @Autowired
    VectorStore vectorStore;

    @Autowired
    ChatClient chatClient;

    @Test
    void testRag() {
        vectorStore.add(List.of(new Document("我们的退款政策是 30 天内全额退款。")));

        QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore).build();

        String answer = chatClient.prompt("退款政策是什么?")
            .advisors(qaAdvisor)
            .call()
            .content();

        assertThat(answer).contains("30 天");
    }
}

顺序是:数据先入库,再提问。注意完整链路还依赖 EmbeddingModel,测试里用 Ollama 或 OpenAI 都行,两个容器一起起。

向量库容器

只想测向量库本身时,单独起一个容器就行。Chroma、Qdrant、Milvus、Weaviate 都有对应容器类型,用法一样:

import org.testcontainers.chromadb.ChromaDBContainer;

@Container
static ChromaDBContainer chroma = new ChromaDBContainer("chromadb/chroma");

支持的服务连接(节选):

连接详情容器类型
OllamaConnectionDetailsOllamaContainer
ChromaConnectionDetailsChromaDBContainer
QdrantConnectionDetailsQdrantContainer
WeaviateConnectionDetailsWeaviateContainer
MilvusServiceClientConnectionDetailsMilvusContainer
AwsOpenSearchConnectionDetailsLocalStackContainer

40.6 测试最佳实践

固定随机性。 集成测试把 temperature 设为 0,回答才稳定。评估类测试别用流式调用。同一个测试跑三遍结果一致,才谈得上可信。

用评估器断言。 别只断言”非空”。RAG 测试跑完用 RelevancyEvaluator 判相关性,比字符串匹配靠谱。用法见第 37、38 章。

别断言完整句子。 回答有随机性,全等断言会偶发失败。用包含关键词,或者更稳的评估器。

控制成本。 集成测试跑本地小模型(Ollama),别打远程付费 API。评估用专用小模型,比如 Bespoke Minicheck。

保证隔离。 每个测试用独立容器或独立集合,别共享状态。数据类测试做好清理。

清理测试数据。 向量库测试会污染数据。测试用的集合加前缀,跑完删除;或者每个测试类用独立集合,互不干扰。

挂 CI。 评估测试进流水线,改检索参数、换模型都能自动发现回退。

评估测试和集成测试可以合并:Testcontainers 起真实依赖,评估器断言质量。这是生产级 RAG 项目的标配测试,第 37、38 章的评估器在这里用上。

40.7 小结

AI 应用测试分三层:mock 掉模型的单元测试、真实容器的集成测试、评估器把关的质量测试。Development-time Services 让开发期本地起依赖,Testcontainers 让测试环境干净隔离。固定随机性、控制成本、挂 CI,测试才可靠。