Golang中如何模拟包方法进行测试

Golang中如何模拟包方法进行测试 我想测试一个使用 pgxpool 包的函数。 在函数内部,我通过 Acquire 方法获取一个连接 (*Conn)。 然后我在这个连接上使用 Release 和 Exec 方法。 如何模拟这两个方法?

一个针对类似需求(涉及 os 包函数)的答案建议创建一个变量来覆盖 os.Create 函数。

var osCreate = os.Create

如何对 pgxpool.Release 做同样的事情? 谢谢

2 回复

我的猜测是,必须有可能临时覆盖符号表,并且存在实现此功能的包。

更多关于Golang中如何模拟包方法进行测试的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中模拟包方法进行测试时,通常使用接口抽象和依赖注入。对于pgxpool,你可以通过创建自定义接口来模拟AcquireReleaseExec方法。以下是一个示例:

首先,定义接口来抽象连接池和连接的行为:

package mypackage

import (
    "context"
    "github.com/jackc/pgx/v5"
    "github.com/jackc/pgx/v5/pgxpool"
)

// 定义连接池接口
type Pool interface {
    Acquire(ctx context.Context) (Conn, error)
}

// 定义连接接口
type Conn interface {
    Release()
    Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error)
}

// 包装pgxpool.Pool实现Pool接口
type PoolWrapper struct {
    pool *pgxpool.Pool
}

func (pw *PoolWrapper) Acquire(ctx context.Context) (Conn, error) {
    conn, err := pw.pool.Acquire(ctx)
    if err != nil {
        return nil, err
    }
    return &ConnWrapper{conn: conn}, nil
}

// 包装pgxpool.Conn实现Conn接口
type ConnWrapper struct {
    conn *pgxpool.Conn
}

func (cw *ConnWrapper) Release() {
    cw.conn.Release()
}

func (cw *ConnWrapper) Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) {
    return cw.conn.Exec(ctx, sql, arguments...)
}

然后,在你的业务函数中接受接口类型:

func MyFunction(ctx context.Context, pool Pool) error {
    conn, err := pool.Acquire(ctx)
    if err != nil {
        return err
    }
    defer conn.Release()
    
    _, err = conn.Exec(ctx, "INSERT INTO users(name) VALUES($1)", "John")
    return err
}

在测试中,你可以创建模拟实现:

package mypackage_test

import (
    "context"
    "errors"
    "testing"
    "github.com/jackc/pgx/v5/pgconn"
)

// 模拟连接
type MockConn struct {
    ReleaseCalled bool
    ExecCalled    bool
    ExecError     error
}

func (m *MockConn) Release() {
    m.ReleaseCalled = true
}

func (m *MockConn) Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) {
    m.ExecCalled = true
    return pgconn.NewCommandTag("INSERT 0 1"), m.ExecError
}

// 模拟连接池
type MockPool struct {
    AcquireError error
    Conn         *MockConn
}

func (m *MockPool) Acquire(ctx context.Context) (mypackage.Conn, error) {
    if m.AcquireError != nil {
        return nil, m.AcquireError
    }
    return m.Conn, nil
}

func TestMyFunction(t *testing.T) {
    ctx := context.Background()
    
    // 测试正常情况
    mockConn := &MockConn{}
    mockPool := &MockPool{Conn: mockConn}
    
    err := MyFunction(ctx, mockPool)
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }
    
    if !mockConn.ReleaseCalled {
        t.Error("Release was not called")
    }
    
    if !mockConn.ExecCalled {
        t.Error("Exec was not called")
    }
    
    // 测试错误情况
    mockConnWithError := &MockConn{ExecError: errors.New("exec error")}
    mockPoolWithError := &MockPool{Conn: mockConnWithError}
    
    err = MyFunction(ctx, mockPoolWithError)
    if err == nil {
        t.Error("Expected error, got nil")
    }
}

这种方法通过接口实现了依赖注入,使得测试时可以轻松替换真实实现为模拟对象。对于pgxpool这种具体类型,你需要创建包装器来适配接口,然后在生产代码中使用包装器,在测试代码中使用模拟实现。

回到顶部