模拟sql查询Golang

ozxc1zmp  于 2023-08-01  发布在  Go
关注(0)|答案(3)|浏览(111)

我有一个函数:

func (db *DBSqlx) GetRefreshToken(oldToken string, tx *sqlx.Tx) (string, error) {
    var refreshToken string

    err := db.TemplateGet(
        tx,
        &refreshToken,
        `query`,
        oldToken,
    )

    if err != nil {
        return "", errors.Wrap(err, "не удалось запросить рефреш токен для указанного токена")
    }

    return refreshToken, err
}

字符串
如何用mock response为这个函数编写测试?
DB具有类型:

type DBSqlx struct {
    PgConn    *sqlx.DB
    PgConnCtx context.Context
}


我试着写了这个代码。但我不知道如何正确使用该软件包。

db, mock, err := sqlmock.New()
if err != nil {
    t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()

mock.ExpectQuery("query").WillReturnRows()

xsuvu9jc

xsuvu9jc1#

您可以使用一些 Package 器抽象您的存储和底层数据库句柄(使其对纯数据库和tx有用),然后用另一个存根接口替换它。它甚至不需要在代码库中包含额外的库。
您应该记住真实的数据库、NULL值等潜在的序列化问题,使用https://github.com/ory/dockertest添加一些真实数据的集成测试
但是对于简单的箱子, Package 纸就足够了

// root package 
type TokenStorage interface {
    GetToken(ctx context.Context, oldToken string) (string, error)
}

// yourtestfile_test.go
type TokenStorageStub struct {}

func (t *TokenStorageStub) GetToken(ctx context.Context, oldToken string) (string, error) {
    b := make([]byte, 16)
    n, err := rand.Read(b)
    if n != len(b) || err != nil {
        return "", fmt.Errorf("could not successfully read from the system CSPRNG.")
    }
    return hex.EncodeToString(b), nil
}

// postgres || mysql || sqlite package 

// TokenStorage impelements root interface using postgres. 
type TokenStorage struct {
    db ExtendedDB 
}

func NewTokenStorage(db ExtendedDB) (*TokenStorage, error) {
    if db == nil {
        return nil, errors.New("provided db handle is nil")
    }
    return &TokenStorage{db: db}, nil 
}

func (s *TokenStorage) GetToken(ctx context.Context, oldToken string) (string, error) {
    const query = `SELECT ...`
    var token string 
    if err := s.db.QueryRowContext(ctx, query, oldToken).Scan(&token); err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, rootpkg.ErrTokenNotFound
        }
        return nil, fmt.Errorf("postgres: problem while get token using old token: %w", err)
    }
    return token, nil
}

// Queryer is an interface used for selection queries.
type Queryer interface {
    QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
    QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}

// Execer is an interface used for executing queries.
type Execer interface {
    ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}

// ExtendedDB is a union interface which can query, and exec, with Context.
type ExtendedDB interface {
    Queryer
    Execer
}

字符串

t5fffqht

t5fffqht2#

您可以使用类似https://github.com/DATA-DOG/go-sqlmock的东西来模拟数据库查询,并通过数据库对响应进行各种控制。
尽管应该注意,在测试中模拟数据库查询通常不被认为是良好的测试实践,因为它最有可能测试的是程序的实际实现,而不是它的行为。

hmae6n7t

hmae6n7t3#

这是Golang中大多数现有sql客户端的一个问题,它们并没有真正被创建为可模拟的,所以像我们通常所做的那样编写一个mock(使用一个接口来与它解耦)是行不通的。
为了更清楚地说明为什么使用接口来解耦然后模拟接口不起作用,原因是因为Query和Exec函数返回的结构具有您无法更改的私有属性。
例如,如果你创建一个这样的接口:

type MyDB interface{
    Exec(query string, args ...any) (sql.Result, error)
    Query(query string, args ...any) (*sql.Rows, error)
}

字符串
你可以这样写一个mock:

type MyDBMock struct{
    // ...
}

func (m MyDBMock) Exec(query string, args ...any) (sql.Result, error) {
    // ... do some mocky thing ...
    return sql.Result{
        // nothing to do here, because all atributes are private
    }
}

func (m MyDBMock) Query(query string, args ...any) (*sql.Rows, error) {
    // ... do some mocky thing ...
    return &sql.Rows{
        // nothing to do here, because all atributes are private
    }
}


但是因为你不能设置sql.Rowssql.Results的值,所以只能为这两个结构体返回零值,如果你正在编写测试,这几乎不是你所需要的。

database/sql最常用的mocking技术

另一种方法是模拟用于连接到数据库的整个驱动程序,这是一个大量工作,利润很少。
幸运的是,这正是在其他答案中提到的https://github.com/DATA-DOG/go-sqlmock库所做的,而且它在这方面做得很好。实际上,这是我所知道的使用现有sql库的唯一方法。
也许不嘲笑是个更好的主意
在编写测试时模拟数据库请求被广泛认为是一种不好的做法,因为知道SQL代码是否工作的最好/唯一方法是将其发送到数据库的真实的示例并在那里运行它。
因此,模拟它可能会导致您在测试中出现假阳性,然后在生产中出现问题。
如果你想走这条路,有一个有趣的技术使用https://github.com/ory/dockertest,你可以直接从Golang代码在docker容器内启动你的数据库示例,然后在测试结束时停止这台机器。
这为您提供了单元测试的大部分好处,但实际上允许您在真实的的数据库中运行查询。这是我最喜欢的测试数据库的方法。
这里有一个关于postgres的例子,如果有帮助的话,我实际上有一个关于here中每个最大的SQL DBMS的例子。

使用一个库,实际上可以帮助您模拟它

免责声明:我指的是我在这一节中自己编写的一个库
在某些情况下,您可能希望模拟数据库调用,以便可以在较小的更快的测试中测试业务逻辑,然后使用其他较慢的测试来测试实际查询。这种情况并不常见,但我确实为Go写了一个非常好的SQL库,在许多其他东西中,它还提供了一个可以开箱即用的mock,所以这也是一个选择:
https://github.com/vingarcia/ksql
这里解释了如何使用这个库的内置模拟:
https://github.com/VinGarcia/ksql/wiki/Testing-Tools-and-ksql.Mock

相关问题