Eino Mock系统:测试替身与模拟对象

Eino Mock系统:测试替身与模拟对象

【免费下载链接】eino 【免费下载链接】eino 项目地址: https://gitcode.com/GitHub_Trending/ei/eino

引言:为什么需要Mock系统?

在LLM应用开发中,测试往往面临巨大挑战:外部API调用成本高、响应不稳定、网络延迟影响测试效率。Eino的Mock系统正是为解决这些痛点而生,它提供了一套完整的测试替身(Test Double)解决方案,让开发者能够在隔离环境中进行可靠的单元测试和集成测试。

"好的测试不应该依赖于外部服务的可用性" —— Eino设计哲学

Mock系统架构概览

Eino的Mock系统采用分层架构设计,完美匹配其组件化理念:

mermaid

核心设计原则

  1. 接口契约优先:所有Mock实现严格遵循对应接口定义
  2. 行为可控性:支持精确配置Mock对象的行为和返回值
  3. 线程安全性:Mock对象在多线程环境下安全使用
  4. 易于集成:与标准测试框架无缝集成

主要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类名主要方法测试场景
ChatModelMockBaseChatModelGenerate, Stream对话生成测试
RetrieverMockRetrieverRetrieve检索功能测试
EmbeddingMockEmbeddingEmbed向量化测试
IndexerMockIndexerIndex, Search索引操作测试
DocumentMockDocumentLoaderLoad文档加载测试

高级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组件、丰富的测试模式和最佳实践,开发者可以:

  1. 提高测试可靠性:摆脱对外部服务的依赖
  2. 加速测试执行:消除网络延迟和API限制
  3. 覆盖边缘场景:轻松测试各种错误和异常情况
  4. 保证代码质量:实现高覆盖率的单元测试和集成测试

随着Eino生态的不断发展,Mock系统将继续演进,提供更多高级特性如:

  • 智能Mock数据生成
  • 测试覆盖率自动分析
  • 性能基准测试工具
  • 可视化测试报告

掌握Eino Mock系统,让你的LLM应用测试变得更加高效、可靠和愉悦!

【免费下载链接】eino 【免费下载链接】eino 项目地址: https://gitcode.com/GitHub_Trending/ei/eino

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值