Eino集成测试:端到端测试方案

Eino集成测试:端到端测试方案

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

概述

在LLM(Large Language Model,大语言模型)应用开发中,集成测试是确保系统各组件协同工作的关键环节。Eino作为Go语言的LLM应用开发框架,提供了强大的编排能力和组件抽象,但同时也带来了测试复杂性的挑战。本文将深入探讨Eino项目的集成测试策略,提供端到端的测试解决方案。

Eino测试架构解析

测试层次结构

Eino项目采用分层测试策略,确保从单元到集成的全面覆盖:

mermaid

核心测试组件

Eino的测试架构围绕以下几个核心概念构建:

测试类型测试目标技术实现
组件测试验证单个组件功能Mock对象 + 接口测试
Graph测试验证编排逻辑真实组件 + 模拟数据
流程测试验证端到端流程完整组件链 + 真实数据
回调测试验证切面逻辑Callback Handler注入

集成测试环境搭建

测试依赖配置

在开始集成测试前,需要配置适当的测试环境:

// go.mod 测试依赖配置
require (
    github.com/stretchr/testify v1.9.0      // 断言库
    go.uber.org/mock v0.4.0                 // Mock框架
    github.com/smartystreets/goconvey v1.8.1 // 测试工具
)

Mock组件实现

Eino提供了丰富的Mock组件,用于隔离测试:

// 使用Mock ChatModel进行测试
mockModel := &model.ChatModelMock{
    GenerateFunc: func(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.Message, error) {
        return &schema.Message{
            Role:    schema.Assistant,
            Content: "Mock response for testing",
        }, nil
    },
    StreamFunc: func(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.StreamReader[*schema.Message], error) {
        // 实现流式响应模拟
    },
}

Graph编排测试策略

基础Graph测试用例

func TestSingleGraph(t *testing.T) {
    const (
        nodeOfModel  = "model"
        nodeOfPrompt = "prompt"
    )

    ctx := context.Background()
    g := NewGraph[map[string]any, *schema.Message]()

    // 创建Prompt模板节点
    pt := prompt.FromMessages(schema.FString,
        schema.UserMessage("what's the weather in {location}?"),
    )
    err := g.AddChatTemplateNode("prompt", pt)
    assert.NoError(t, err)

    // 创建Mock ChatModel节点
    cm := &chatModel{
        msgs: []*schema.Message{
            {
                Role:    schema.Assistant,
                Content: "the weather is good",
            },
        },
    }
    err = g.AddChatModelNode(nodeOfModel, cm, WithNodeName("MockChatModel"))
    assert.NoError(t, err)

    // 构建Graph连接
    err = g.AddEdge(START, nodeOfPrompt)
    assert.NoError(t, err)
    err = g.AddEdge(nodeOfPrompt, nodeOfModel)
    assert.NoError(t, err)
    err = g.AddEdge(nodeOfModel, END)
    assert.NoError(t, err)

    // 编译并执行Graph
    r, err := g.Compile(context.Background(), WithMaxRunSteps(10))
    assert.NoError(t, err)

    // 测试不同输入场景
    testCases := []struct {
        name     string
        input    map[string]any
        expected string
        hasError bool
    }{
        {"正常输入", map[string]any{"location": "beijing"}, "the weather is good", false},
        {"错误输入", map[string]any{"wrong key": 1}, "", true},
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            result, err := r.Invoke(ctx, tc.input)
            if tc.hasError {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
                assert.Equal(t, tc.expected, result.Content)
            }
        })
    }
}

流式处理测试

Eino支持完整的流式处理,测试时需要验证各种流式场景:

func TestStreamProcessing(t *testing.T) {
    // 测试流式Invoke
    s, err := r.Stream(ctx, in)
    assert.NoError(t, err)
    
    // 验证流式数据收集
    result, err := concatStreamReader(s)
    assert.NoError(t, err)
    assert.Contains(t, result, "expected content")

    // 测试Transform模式
    sr, sw := schema.Pipe[map[string]any](1)
    _ = sw.Send(in, nil)
    sw.Close()
    
    transformed, err := r.Transform(ctx, sr)
    assert.NoError(t, err)
    
    // 验证流式转换
    finalResult, err := concatStreamReader(transformed)
    assert.NoError(t, err)
}

嵌套Graph测试方案

多层级Graph集成

func TestNestedGraph(t *testing.T) {
    const (
        nodeOfLambda1  = "lambda1"
        nodeOfLambda2  = "lambda2"
        nodeOfSubGraph = "sub_graph"
    )

    ctx := context.Background()
    g := NewGraph[string, *schema.Message]()
    
    // 创建子Graph
    sg := NewGraph[map[string]any, *schema.Message]()
    
    // 配置子Graph组件
    pt := prompt.FromMessages(schema.FString,
        schema.UserMessage("what's the weather in {location}?"),
    )
    err := sg.AddChatTemplateNode("prompt", pt)
    assert.NoError(t, err)

    cm := &chatModel{
        msgs: []*schema.Message{
            {Role: schema.Assistant, Content: "the weather is good"},
        },
    }
    err = sg.AddChatModelNode("model", cm, WithNodeName("MockChatModel"))
    assert.NoError(t, err)

    // 构建子Graph连接
    err = sg.AddEdge(START, "prompt")
    assert.NoError(t, err)
    err = sg.AddEdge("prompt", "model")
    assert.NoError(t, err)
    err = sg.AddEdge("model", END)
    assert.NoError(t, err)

    // 在主Graph中集成子Graph
    l1 := InvokableLambda[string, map[string]any](
        func(ctx context.Context, input string) (output map[string]any, err error) {
            return map[string]any{"location": input}, nil
        })

    l2 := InvokableLambda[*schema.Message, *schema.Message](
        func(ctx context.Context, input *schema.Message) (output *schema.Message, err error) {
            input.Content = fmt.Sprintf("processed: %s", input.Content)
            return input, nil
        })

    err = g.AddLambdaNode(nodeOfLambda1, l1, WithNodeName("Lambda1"))
    assert.NoError(t, err)
    err = g.AddGraphNode(nodeOfSubGraph, sg, WithNodeName("SubGraphName"))
    assert.NoError(t, err)
    err = g.AddLambdaNode(nodeOfLambda2, l2, WithNodeName("Lambda2"))
    assert.NoError(t, err)

    // 构建完整数据流
    err = g.AddEdge(START, nodeOfLambda1)
    assert.NoError(t, err)
    err = g.AddEdge(nodeOfLambda1, nodeOfSubGraph)
    assert.NoError(t, err)
    err = g.AddEdge(nodeOfSubGraph, nodeOfLambda2)
    assert.NoError(t, err)
    err = g.AddEdge(nodeOfLambda2, END)
    assert.NoError(t, err)

    // 编译和执行
    r, err := g.Compile(context.Background(), WithMaxRunSteps(10))
    assert.NoError(t, err)

    // 验证嵌套Graph功能
    result, err := r.Invoke(ctx, "london")
    assert.NoError(t, err)
    assert.Equal(t, "processed: the weather is good", result.Content)
}

回调机制测试

Callback注入测试

Eino的切面机制(Aspect)通过Callback实现,测试时需要验证回调的正确触发:

func TestCallbackInjection(t *testing.T) {
    ck := "callback_depth"
    cb := callbacks.NewHandlerBuilder().
        OnStartFn(func(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context {
            // 记录回调深度
            v, ok := ctx.Value(ck).(int)
            if ok {
                v++
            }
            return context.WithValue(ctx, ck, v)
        }).
        OnStartWithStreamInputFn(func(ctx context.Context, info *callbacks.RunInfo, input *schema.StreamReader[callbacks.CallbackInput]) context.Context {
            input.Close()
            v, ok := ctx.Value(ck).(int)
            if ok {
                v++
            }
            return context.WithValue(ctx, ck, v)
        }).Build()

    // 测试各种执行模式下的回调
    testModes := []struct {
        name string
        testFunc func() error
    }{
        {"Invoke模式", func() error {
            _, err := r.Invoke(ctx, "london", WithCallbacks(cb))
            return err
        }},
        {"Stream模式", func() error {
            rs, err := r.Stream(ctx, "london", WithCallbacks(cb))
            if err != nil {
                return err
            }
            for {
                _, err = rs.Recv()
                if err == io.EOF {
                    break
                }
                if err != nil {
                    return err
                }
            }
            return nil
        }},
    }

    for _, mode := range testModes {
        t.Run(mode.name, func(t *testing.T) {
            err := mode.testFunc()
            assert.NoError(t, err)
            // 验证回调被正确触发
            depth := ctx.Value(ck).(int)
            assert.Greater(t, depth, 0)
        })
    }
}

类型安全测试

运行时类型检查

Eino在Graph编排时进行编译期类型检查,但运行时仍需验证类型安全:

func TestRuntimeTypeSafety(t *testing.T) {
    // 测试Any类型的运行时检查
    anyG := NewGraph[any, string]()
    err := anyG.AddLambdaNode("node1", InvokableLambda(func(ctx context.Context, input string) (output any, err error) {
        return input + "node1", nil
    }))
    assert.NoError(t, err)

    err = anyG.AddLambdaNode("node2", InvokableLambda(func(ctx context.Context, input string) (output any, err error) {
        return input + "node2", nil
    }))
    assert.NoError(t, err)

    // 构建Graph
    err = anyG.AddEdge(START, "node1")
    assert.NoError(t, err)
    err = anyG.AddEdge("node1", "node2")
    assert.NoError(t, err)
    err = anyG.AddEdge("node2", END)
    assert.NoError(t, err)

    r, err := anyG.Compile(context.Background())
    assert.NoError(t, err)

    // 测试正常类型
    result, err := r.Invoke(context.Background(), "start")
    assert.NoError(t, err)
    assert.Equal(t, "startnode1node2", result)

    // 测试类型错误场景
    anyG = NewGraph[any, string]()
    err = anyG.AddLambdaNode("node1", InvokableLambda(func(ctx context.Context, input string) (output any, err error) {
        return 123 // 返回错误类型
    }))
    assert.NoError(t, err)

    err = anyG.AddLambdaNode("node2", InvokableLambda(func(ctx context.Context, input string) (output any, err error) {
        return input + "node2", nil
    }))
    assert.NoError(t, err)

    // 构建并测试类型错误
    err = anyG.AddEdge(START, "node1")
    assert.NoError(t, err)
    err = anyG.AddEdge("node1", "node2")
    assert.NoError(t, err)
    err = anyG.AddEdge("node2", END)
    assert.NoError(t, err)

    r, err = anyG.Compile(context.Background())
    assert.NoError(t, err)

    _, err = r.Invoke(context.Background(), "start")
    assert.Error(t, err)
    assert.Contains(t, err.Error(), "runtime type check")
}

分支逻辑测试

条件分支测试

Eino支持基于条件的动态分支,测试时需要覆盖各种分支场景:

func TestConditionalBranches(t *testing.T) {
    g := NewGraph[string, string]()
    
    // 创建分支节点
    err := g.AddLambdaNode("node1", InvokableLambda(func(ctx context.Context, input string) (output any, err error) {
        return input + "node1", nil
    }))
    assert.NoError(t, err)

    // 定义分支条件
    branch := NewGraphBranch(func(ctx context.Context, in string) (endNode string, err error) {
        if len(in) > 5 {
            return "node2", nil
        }
        return "node3", nil
    }, map[string]bool{"node2": true, "node3": true})

    err = g.AddBranch("node1", branch)
    assert.NoError(t, err)

    // 创建不同分支的处理节点
    err = g.AddLambdaNode("node2", InvokableLambda(func(ctx context.Context, input string) (output any, err error) {
        return "long_branch", nil
    }))
    assert.NoError(t, err)

    err = g.AddLambdaNode("node3", InvokableLambda(func(ctx context.Context, input string) (output any, err error) {
        return "short_branch", nil
    }))
    assert.NoError(t, err)

    // 构建Graph
    err = g.AddEdge(START, "node1")
    assert.NoError(t, err)
    err = g.AddEdge("node2", END)
    assert.NoError(t, err)
    err = g.AddEdge("node3", END)
    assert.NoError(t, err)

    r, err := g.Compile(context.Background())
    assert.NoError(t, err)

    // 测试不同分支条件
    testCases := []struct {
        input    string
        expected string
    }{
        {"short", "short_branch"},
        {"very_long_input", "long_branch"},
    }

    for _, tc := range testCases {
        t.Run(fmt.Sprintf("Input: %s", tc.input), func(t *testing.T) {
            result, err := r.Invoke(context.Background(), tc.input)
            assert.NoError(t, err)
            assert.Equal(t, tc.expected, result)
        })
    }
}

性能与并发测试

并发安全测试

Eino的State处理需要保证并发安全:

func TestConcurrencySafety(t *testing.T) {
    g := NewGraph[string, int]()
    
    // 创建有状态的操作节点
    err := g.AddLambdaNode("counter", InvokableLambda(func(ctx context.Context, input string) (output int, err error) {
        state := GetState(ctx)
        state.Mutex.Lock()
        defer state.Mutex.Unlock()
        state.Counter++
        return state.Counter, nil
    }))
    assert.NoError(t, err)

    err = g.AddEdge(START, "counter")
    assert.NoError(t, err)
    err = g.AddEdge("counter", END)
    assert.NoError(t, err)

    r, err := g.Compile(context.Background())
    assert.NoError(t, err)

    // 并发测试
    const concurrentRequests = 100
    results := make(chan int, concurrentRequests)
    errors := make(chan error, concurrentRequests)

    var wg sync.WaitGroup
    for i := 0; i < concurrentRequests; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            result, err := r.Invoke(context.Background(), "test")
            if err != nil {
                errors <- err
                return
            }
            results <- result
        }()
    }
    wg.Wait()

    close(results)
    close(errors)

    // 验证没有错误且结果唯一
    assert.Empty(t, errors)
    uniqueResults := make(map[int]bool)
    for result := range results {
        uniqueResults[result] = true
    }
    assert.Len(t, uniqueResults, concurrentRequests)
}

测试最佳实践

测试组织结构

建议采用以下测试目录结构:

test/
├── integration/          # 集成测试
│   ├── graph/           # Graph编排测试
│   ├── components/      # 组件集成测试
│   └── workflow/        # 工作流测试
├── performance/         # 性能测试
└── fixtures/           # 测试数据

测试数据管理

使用Table-Driven Tests(表格驱动测试)组织测试用例:

func TestGraphScenarios(t *testing.T) {
    testCases := []struct {
        name        string
        setupGraph  func() *Graph[any, any]
        input       any
        expected    any
        shouldError bool
    }{
        {
            name: "简单Chat流程",
            setupGraph: func() *Graph[any, any] {
                g := NewGraph[any, any]()
                // Graph设置逻辑
                return g
            },
            input:       map[string]any{"query": "Hello"},
            expected:    "Response",
            shouldError: false,
        },
        // 更多测试场景...
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            g := tc.setupGraph()
            r, err := g.Compile(context.Background())
            if tc.shouldError {
                assert.Error(t, err)
                return
            }
            assert.NoError(t, err)
            
            result, err := r.Invoke(context.Background(), tc.input)
            assert.NoError(t, err)
            assert.Equal(t, tc.expected, result)
        })
    }
}

总结

Eino的集成测试需要全面覆盖Graph编排、流式处理、类型安全、回调机制等核心功能。通过本文提供的测试方案,您可以:

  1. 构建可靠的测试基础设施:使用Mock组件和测试工具
  2. 验证复杂编排逻辑:测试嵌套Graph和条件分支
  3. 确保类型安全:运行时类型检查和验证
  4. 测试并发场景:验证State处理的线程安全性
  5. 覆盖全流程:从单元测试到端到端集成测试

遵循这些测试实践,将帮助您构建高质量、可靠的Eino LLM应用程序,确保在生产环境中的稳定性和性能。

测试是质量的守护者,在LLM应用开发中更是如此。通过全面的集成测试,我们不仅验证代码正确性,更确保AI应用的可预测性和可靠性。

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

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

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

抵扣说明:

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

余额充值