2015-07-12 62 views
2

在测试数据库方法时,我创建了一个包含数据库/ sql包的最小包装,以允许我测试接口而不是难以设置具体类。但是,我得到以下错误,当我尝试模拟sql.Stmt:在Go中嘲笑数据库/ sql结构

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或us中使用implements关键字e像C#中的分号子类。为什么它不使用这种返回类型?难道我做错了什么?

+0

'* sql.Rows'没有实现'IStmt',所以你不能返回它。更改界面以返回具体类型(最快修复)。我还建议阅读[Effective Go](https://golang.org/doc/effective_go.html#interface-names)并查看你的界面命名(即'Queryer'或'Queryable')和/或阅读https ://robots.thoughtbot.com/interface-with-your-database-in-go – elithrar

+0

但是* sql.Stmt实现了和我一样的Query方法和QueryRow方法。 Query(args ... interface {})(* Rows,error)'和'QueryRow(args ... interface {})* Row'。当然,我使用接口来表示'* sql.Rows'和'* sql.Row',但我使用的方法签名完全相同。如果我改变这些方法分别返回'* sql.Rows'和'* sql.Row',它就会起作用。如果Go无法处理返回类型的嵌套接口,那真是太遗憾了! –

回答

1

这就是我最终创造出来完成我所需要的。请注意,所有这些都可以在我创建的onedb库中获得:https://github.com/EndFirstCorp/onedb。除了模拟,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基本上数据库/ SQL的分别QueryQueryRow方法的返回的接口。 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 
} 

现在,而不是使用真实的数据库/ SQL,我可以使用sqllibBackend它返回容易mockable DBER接口。