【问题标题】:Mocking database/sql structs in Go在 Go 中模拟数据库/sql 结构
【发布时间】:2015-09-30 14:11:32
【问题描述】:

在测试数据库方法时,我在 database/sql 包上创建了一个最小的包装器,以允许我针对接口进行测试,而不是设置具体的类(如果不是不可能的话)。但是,当我尝试模拟 sql.Stmt 时出现以下错误:

cannot use *sql.Stmt as type IStmt in return argument:
    *sql.Stmt does not implement IStmt (wrong type for Query method)
            have Query(...interface {}) (*sql.Rows, error)
            want Query(...interface {}) (IRows, error)

这是我的界面:

type IStmt interface {
    Query(args ...interface{}) (IRows, error)
    QueryRow(args ...interface{}) IRow
}

type IRows interface {
    Columns() ([]string, error)
    Next() bool
    Close() error
    Scan(dest ...interface{}) error
    Err() error
}

这是问题的方法:

func (c *DbConnection) Prepare(query string) (IStmt, error) {
    return c.conn.Prepare(query)
}

我知道 Go 的一大优点是您可以创建自己的接口,并且实现它的任何结构都会自动“实现”它,而无需在 java 中使用 implements 关键字或使用分号,如C# 到子类。为什么它不适用于此返回类型?我是不是做错了什么?

【问题讨论】:

  • *sql.Rows 没有实现IStmt,所以不能退货。更改接口以返回具体类型(最快修复)。我还建议阅读Effective Go 并查看您的界面命名(即QueryerQueryable)和/或阅读robots.thoughtbot.com/interface-with-your-database-in-go
  • 但是 *sql.Stmt 实现了 Query 方法和 QueryRow 方法,就像我的一样。 Query(args ...interface{}) (*Rows, error)QueryRow(args ...interface{}) *Row。当然,我使用一个接口来表示*sql.Rows*sql.Row,但是我使用的方法签名完全一样。如果我将这些方法更改为分别返回*sql.Rows*sql.Row,它就可以工作。如果 Go 不能处理来自返回类型的嵌套接口,那真是太可惜了!

标签: database unit-testing go


【解决方案1】:

这就是我最终创建的以完成我需要的东西。请注意,所有这些都在我创建的 onedb 库中可用:https://github.com/EndFirstCorp/onedb。除了 mocking,onedb 还可以使用相同的方法查询 Redis 和 OpenLDAP。

import "database/sql"

type rowsScanner interface {
    Columns() ([]string, error)
    Next() bool
    Close() error
    Err() error
    scanner
}

type scanner interface {
    Scan(dest ...interface{}) error
}

type DBer interface {
    Ping() error
    Close() error
    Execute(query string, args ...interface{}) error
    Query(query string, args ...interface{}) (rowsScanner, error)
    QueryRow(query string, args ...interface{}) scanner
}

rowsScannerscanner本质上是分别返回database/sqlQueryQueryRow方法的接口。 DBer 最终是我希望我可以摆脱数据库/sql 的接口,以便我可以模拟它。但是,因为我不能这样做,所以我创建了一个可以进行转换的对象。

type sqllibBackend struct {
    db *sql.DB
    DBer
}

sqllibBackend 是进行转换的神奇结构。它将*sql.DB 方法的输出转换为可模拟的DBer 接口。剩下的就是转换器结构:

func NewSqllib(driverName, connectionString string) (DBer, error) {
    sqlDb, err := sql.Open(driverName, connectionString)
    if err != nil {
        return nil, err
    }
    err = sqlDb.Ping()
    if err != nil {
        return nil, err
    }
    return &sqllibBackend{db: sqlDb}, nil
}

func (b *sqllibBackend) Close() error {
    return b.db.Close()
}

func (b *sqllibBackend) Query(query string, args ...interface{}) (rowsScanner, error) {
    return b.db.Query(query, args...)
}

func (b *sqllibBackend) QueryRow(query string, args ...interface{}) scanner {
    return b.db.QueryRow(query, args...)
}

func (b *sqllibBackend) Execute(query string, args ...interface{}) error {
    _, err := b.db.Exec(query, args...)
    return err
}

现在,我可以使用sqllibBackend,而不是使用真正的数据库/sql,它会返回易于模拟的 DBer 接口。

【讨论】:

    猜你喜欢
    • 2011-01-21
    • 1970-01-01
    • 2020-01-06
    • 2013-11-03
    • 1970-01-01
    • 2017-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多