Database 在Golang中伪造DB方法

Database 在Golang中伪造DB方法,database,unit-testing,go,mocking,Database,Unit Testing,Go,Mocking,我正在尝试为以下函数创建一个单元测试,该函数使用Postgres驱动程序在内部进行数据库调用: type DBer interface { Exec(query string, args ...interface{}) (sql.Result, error) Query(query string, args ...interface{}) (interface{}, error) QueryRow(query string, args ...interface{}) *s

我正在尝试为以下函数创建一个单元测试,该函数使用Postgres驱动程序在内部进行数据库调用:

type DBer interface {
    Exec(query string, args ...interface{}) (sql.Result, error)
    Query(query string, args ...interface{}) (interface{}, error)
    QueryRow(query string, args ...interface{}) *sql.Row
    Prepare(query string) (*sql.Stmt, error)
}

type AppInfoCtrl struct {
    DB DBer
}

type Rower interface {
    Next() bool
    Close() error
    Scan(...interface{}) error
}

func parseRows(rows Rower, infos []AppInfo) ([]AppInfo, error) {
    defer rows.Close()
    for rows.Next() {
        var i AppInfo
        if err := rows.Scan(&i.Id, &i.Platform); err != nil {
            return nil, err
        }
        infos = append(infos, i)
    }
    return infos, nil
}

func (a *AppInfoCtrl) List() ([]AppInfo, error) {
    query := "select id, platform from appinfo where deleted = false"
    rows, err := a.DB.Query(query)
    if err != nil {
        return nil, err
    }

    appInfos := []AppInfo{}
    parseRows(rows, appInfos)

    return appInfos, nil
}
我的测试是这样的:

func (f *FakeDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
    fmt.Println(query)
    var rows *sql.Rows
    return rows, nil
}
但是,运行此命令后,我出现以下编译错误:

appinfo/controller.go:68:11: cannot use rows (type interface {}) as type Rower in argument to parseRows:
        interface {} does not implement Rower (missing Close method)
当我查看源代码时,sql.Rows确实实现了Close():


知道我需要在这里做什么吗?我在这里是否采取了正确的方法来测试List()?我对测试parseRows()并不特别挑剔,因为它只包含对db.Rows的调用,但我至少需要在这里测试List。

问题是
DBer.Query
返回一个通用的
接口{}
,编译器不能假设它有任何方法:

Query(query string, args ...interface{}) (interface{}, error)
这意味着,除非使用类型断言,否则不能对返回值调用任何方法。也许它应该返回
(Rower,error)

实际上,编译器可以看到:

rows, err := a.DB.Query(query)
是一个
接口{}
。它可能是任何东西。它可以是
int
。它不能假设它有任何方法。因此,当您将其传递给
parseRows
时:

parseRows(rows, appInfos)
定义为:

func parseRows(rows Rower, infos []AppInfo) ([]AppInfo, error)
好的,它需要一个
Rower
,但是您正在传递一个
接口{}
,它不能保证实现
Rower
。因此,将出现以下编译器错误:

interface {} does not implement Rower (missing Close method)

它与基础值无关,而是与变量的类型有关。变量的类型为
接口{}
,没有方法,因此不实现
行程序

,感谢您的快速响应。我将类型更改为Rower,但出现以下错误:*sql.DB未实现appinfo.DBer(查询方法的类型错误)have Query(string,…interface{})(*sql.Rows,error)want Query(string,…interface{})(appinfo.Rower,error),这就是我最终将返回类型更改为*sql.Rows的原因。但是,当我运行我的测试rows.Close()时,会遇到以下问题:panic:runtime error:invalid memory address或nil pointer dereference,我需要弄清楚如何创建一个假*sql.rows,在调用Close()时它不会死机,实际上,您必须模拟它,而不仅仅是返回指向实际类型的nil指针。sql.Rows是一个相当大的结构。我希望它是一个界面。我现在正绞尽脑汁想办法模仿它。你不必希望它是一个界面。只需创建所需的接口,并使用它代替您的类型中的
sql.Rows
。这就是Go的duck类型接口的优点——实现不必知道接口。@mohi666其实很简单,看看这个:(固定链接)。使用此选项,Dber的模拟实现可以返回Rower的模拟实现,并且可以在任何时候使用适配器。