Eino Mock系统:测试替身与模拟对象
【免费下载链接】eino 项目地址: https://gitcode.com/GitHub_Trending/ei/eino
引言:为什么需要Mock系统?
在LLM应用开发中,测试往往面临巨大挑战:外部API调用成本高、响应不稳定、网络延迟影响测试效率。Eino的Mock系统正是为解决这些痛点而生,它提供了一套完整的测试替身(Test Double)解决方案,让开发者能够在隔离环境中进行可靠的单元测试和集成测试。
"好的测试不应该依赖于外部服务的可用性" —— Eino设计哲学
Mock系统架构概览
Eino的Mock系统采用分层架构设计,完美匹配其组件化理念:
核心设计原则
- 接口契约优先:所有Mock实现严格遵循对应接口定义
- 行为可控性:支持精确配置Mock对象的行为和返回值
- 线程安全性:Mock对象在多线程环境下安全使用
- 易于集成:与标准测试框架无缝集成
主要Mock组件详解
1. ChatModel Mock
ChatModel是LLM应用的核心组件,Eino为其提供了完整的Mock实现:
// 创建ChatModel Mock实例
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockModel := model.NewMockBaseChatModel(ctrl)
// 配置预期行为
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Return(&schema.Message{
Role: schema.RoleAssistant,
Content: "这是Mock生成的回复",
}, nil)
// 在测试中使用
result, err := mockModel.Generate(ctx, messages)
支持的Mock功能:
| 功能类型 | 方法 | 描述 |
|---|---|---|
| 同步生成 | Generate() | 模拟同步消息生成 |
| 流式生成 | Stream() | 模拟流式消息输出 |
| 工具绑定 | BindTools() | 模拟工具绑定行为 |
| 工具调用 | WithTools() | 模拟工具调用场景 |
2. Retriever Mock
检索器Mock支持多种检索场景的模拟:
// 创建Retriever Mock
mockRetriever := retriever.NewMockRetriever(ctrl)
// 配置检索结果
expectedDocs := []*schema.Document{
{Content: "检索结果1", Metadata: map[string]any{"score": 0.95}},
{Content: "检索结果2", Metadata: map[string]any{"score": 0.88}},
}
mockRetriever.EXPECT().
Retrieve(gomock.Any(), gomock.Any()).
Return(expectedDocs, nil)
3. 完整组件Mock矩阵
Eino Mock系统覆盖了所有核心组件类型:
| 组件类型 | Mock类名 | 主要方法 | 测试场景 |
|---|---|---|---|
| ChatModel | MockBaseChatModel | Generate, Stream | 对话生成测试 |
| Retriever | MockRetriever | Retrieve | 检索功能测试 |
| Embedding | MockEmbedding | Embed | 向量化测试 |
| Indexer | MockIndexer | Index, Search | 索引操作测试 |
| Document | MockDocumentLoader | Load | 文档加载测试 |
高级Mock技巧与实践
1. 动态行为配置
// 根据输入动态返回不同结果
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.Message, error) {
// 分析输入消息内容
if strings.Contains(messages[0].Content, "天气") {
return &schema.Message{Content: "今天天气晴朗"}, nil
}
return &schema.Message{Content: "默认回复"}, nil
})
2. 错误场景模拟
// 模拟各种错误场景
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Return(nil, errors.New("API调用超时"))
// 模拟速率限制错误
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Return(nil, &model.RateLimitError{RetryAfter: 60})
3. 流式响应Mock
// 模拟流式响应
mockModel.EXPECT().
Stream(gomock.Any(), gomock.Any()).
Return(schema.NewStreamReaderFromSlice([]*schema.Message{
{Content: "正在思考"},
{Content: "正在生成"},
{Content: "生成完成"},
}), nil)
测试模式与最佳实践
1. 单元测试模式
func TestChatChain_WithMock(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 创建所有依赖的Mock
mockTemplate := prompt.NewMockChatTemplate(ctrl)
mockModel := model.NewMockBaseChatModel(ctrl)
// 配置Mock行为
mockTemplate.EXPECT().Format(gomock.Any()).Return([]*schema.Message{...})
mockModel.EXPECT().Generate(gomock.Any(), gomock.Any()).Return(...)
// 构建测试链
chain := NewChain().AppendChatTemplate(mockTemplate).AppendChatModel(mockModel)
// 执行测试断言
result, err := chain.Invoke(ctx, input)
assert.NoError(t, err)
assert.Contains(t, result.Content, "预期内容")
}
2. 集成测试模式
func TestReActAgent_Integration(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 创建混合环境:真实组件 + Mock组件
realTools := []*schema.ToolInfo{...}
mockModel := model.NewMockToolCallingChatModel(ctrl)
// 配置工具调用场景
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Return(&schema.Message{
Content: "",
ToolCalls: []*schema.ToolCall{{
Name: "get_weather",
Arguments: `{"city": "北京"}`,
}},
}, nil)
// 测试完整流程
agent := react.NewAgent(mockModel, realTools)
result, err := agent.Run(ctx, "北京天气怎么样?")
assert.NoError(t, err)
assert.True(t, result.ContainsToolCall)
}
3. 性能测试模式
func BenchmarkChatModel_Performance(b *testing.B) {
ctrl := gomock.NewController(b)
defer ctrl.Finish()
mockModel := model.NewMockBaseChatModel(ctrl)
mockModel.EXPECT().Generate(gomock.Any(), gomock.Any()).AnyTimes().Return(...)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := mockModel.Generate(ctx, testMessages)
if err != nil {
b.Fatal(err)
}
}
}
Mock系统的高级特性
1. 状态验证
// 验证方法调用次数和参数
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Times(3) // 必须被调用3次
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
With(gomock.Any(), gomock.Len(2)) // 第二个参数必须是长度为2的切片
2. 调用顺序验证
// 验证方法调用顺序
gomock.InOrder(
mockModel.EXPECT().Generate(gomock.Any(), gomock.Any()).Return(...),
mockTool.EXPECT().Execute(gomock.Any(), gomock.Any()).Return(...),
mockModel.EXPECT().Generate(gomock.Any(), gomock.Any()).Return(...)
)
3. 自定义匹配器
// 创建自定义参数匹配器
func containsWeatherQuery(msgs []*schema.Message) bool {
for _, msg := range msgs {
if strings.Contains(msg.Content, "天气") {
return true
}
}
return false
}
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
With(gomock.Any(), gomock.CustomMatcher(containsWeatherQuery))
实战案例:完整的测试套件
案例1:对话链测试
func TestDialogueChain_CompleteFlow(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 创建所有Mock组件
mockTemplate := prompt.NewMockChatTemplate(ctrl)
mockModel := model.NewMockBaseChatModel(ctrl)
mockRetriever := retriever.NewMockRetriever(ctrl)
// 配置预期行为序列
mockRetriever.EXPECT().
Retrieve(gomock.Any(), "用户查询").
Return([]*schema.Document{{Content: "相关文档内容"}}, nil)
mockTemplate.EXPECT().
Format(gomock.Any()).
Return([]*schema.Message{
schema.SystemMessage("你是有用的助手"),
schema.UserMessage("用户查询\n相关文档内容"),
})
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Return(schema.AssistantMessage("基于文档的回复"), nil)
// 构建和测试完整链
chain := NewChain().
AppendRetriever(mockRetriever).
AppendChatTemplate(mockTemplate).
AppendChatModel(mockModel)
result, err := chain.Invoke(ctx, "用户查询")
assert.NoError(t, err)
assert.Equal(t, "基于文档的回复", result.Content)
}
案例2:错误处理测试
func TestChain_ErrorHandling(t *testing.T) {
testCases := []struct {
name string
setupMocks func(*gomock.Controller)
expectedError string
}{
{
name: "API调用超时",
setupMocks: func(ctrl *gomock.Controller) {
mockModel := model.NewMockBaseChatModel(ctrl)
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Return(nil, errors.New("API timeout"))
},
expectedError: "API timeout",
},
{
name: "无效输入处理",
setupMocks: func(ctrl *gomock.Controller) {
mockModel := model.NewMockBaseChatModel(ctrl)
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
Return(nil, &model.InvalidInputError{Details: "empty messages"})
},
expectedError: "empty messages",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
tc.setupMocks(ctrl)
// 执行测试并验证错误处理
})
}
}
性能优化技巧
1. Mock对象复用
var (
mockModel *model.MockBaseChatModel
mockCtrl *gomock.Controller
once sync.Once
)
func getSharedMocks(t *testing.T) (*gomock.Controller, *model.MockBaseChatModel) {
once.Do(func() {
mockCtrl = gomock.NewController(t)
mockModel = model.NewMockBaseChatModel(mockCtrl)
})
return mockCtrl, mockModel
}
2. 批量配置
// 批量配置多个预期
func setupCommonMocks(mockModel *model.MockBaseChatModel) {
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
AnyTimes().
DoAndReturn(func(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.Message, error) {
// 统一的Mock逻辑
return &schema.Message{
Role: schema.RoleAssistant,
Content: "Mock响应: " + messages[len(messages)-1].Content,
}, nil
})
}
常见问题与解决方案
Q1: Mock对象如何验证接口实现?
// 编译时接口实现验证
var _ model.BaseChatModel = (*model.MockBaseChatModel)(nil)
var _ retriever.Retriever = (*retriever.MockRetriever)(nil)
Q2: 如何处理并发测试?
func TestConcurrentAccess(t *testing.T) {
ctrl := gomock.NewController(t)
mockModel := model.NewMockBaseChatModel(ctrl)
// 配置线程安全的Mock
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
AnyTimes().
Return(&schema.Message{Content: "响应"}, nil)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := mockModel.Generate(ctx, testMessages)
assert.NoError(t, err)
}()
}
wg.Wait()
}
Q3: 如何测试超时场景?
func TestTimeoutScenario(t *testing.T) {
ctrl := gomock.NewController(t)
mockModel := model.NewMockBaseChatModel(ctrl)
// 模拟慢速响应
mockModel.EXPECT().
Generate(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.Message, error) {
select {
case <-ctx.Done():
return nil, context.DeadlineExceeded
case <-time.After(2 * time.Second):
return &schema.Message{Content: "响应"}, nil
}
})
// 设置短超时
shortCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel()
_, err := mockModel.Generate(shortCtx, testMessages)
assert.ErrorIs(t, err, context.DeadlineExceeded)
}
总结与展望
Eino的Mock系统为LLM应用测试提供了强大而灵活的工具集。通过精心设计的Mock组件、丰富的测试模式和最佳实践,开发者可以:
- 提高测试可靠性:摆脱对外部服务的依赖
- 加速测试执行:消除网络延迟和API限制
- 覆盖边缘场景:轻松测试各种错误和异常情况
- 保证代码质量:实现高覆盖率的单元测试和集成测试
随着Eino生态的不断发展,Mock系统将继续演进,提供更多高级特性如:
- 智能Mock数据生成
- 测试覆盖率自动分析
- 性能基准测试工具
- 可视化测试报告
掌握Eino Mock系统,让你的LLM应用测试变得更加高效、可靠和愉悦!
【免费下载链接】eino 项目地址: https://gitcode.com/GitHub_Trending/ei/eino
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



