测试与 Testcontainers
本教程共 45 篇 · 第 40 篇 · 更新于 2026-08-16 · 约 8 分钟阅读
本节目标:搞懂 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。
支持的连接(节选):
| 连接详情 | 匹配容器 |
|---|---|
OllamaConnectionDetails | ollama/ollama |
ChromaConnectionDetails | chromadb/chroma |
QdrantConnectionDetails | qdrant/qdrant |
WeaviateConnectionDetails | semitechnologies/weaviate |
MilvusServiceClientConnectionDetails | milvusdb/milvus |
McpSseClientConnectionDetails | docker/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 直接可用。
NoteOllama 镜像不带模型,测试启动后要先拉模型。首次拉镜像、拉模型都慢,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");
支持的服务连接(节选):
| 连接详情 | 容器类型 |
|---|---|
OllamaConnectionDetails | OllamaContainer |
ChromaConnectionDetails | ChromaDBContainer |
QdrantConnectionDetails | QdrantContainer |
WeaviateConnectionDetails | WeaviateContainer |
MilvusServiceClientConnectionDetails | MilvusContainer |
AwsOpenSearchConnectionDetails | LocalStackContainer |
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,测试才可靠。