使用接口来模拟数据库返回类型

huangapple go评论80阅读模式
英文:

Mocking database return types with interfaces

问题

如何正确地使用接口来模拟一些 *sql.DB 方法进行单元测试?例如,我想要一个 Store 结构体,作为我的处理程序的数据源。它有一个 conn 字段,类型为 *sql.DB。但是在我的测试中,我想要模拟数据库,所以我尝试返回的不是实际的 *sql.Rows,而是一个 *sql.Rows 应该满足的接口。以下是代码示例:

type TestRows interface {
	Scan()
}

type TestDB interface {
	Query(query string, args ...interface{}) (TestRows, error)
}

type Rows struct {
	//..
}

func (r *Rows) Scan() {
	fmt.Println("Scanning")
}

type DB struct {
	//...
}

func (d *DB) Query(query string, args ...interface{}) (*Rows, error) {
	return &Rows{/* .... */}, nil
}

type Store struct {
	conn TestDB
}

func main() {
	cl := Store{conn: &DB{}}
	fmt.Println("Hello, playground")
}

在实例化 Store 时,会抛出错误:

cannot use DB literal (type *DB) as type TestDB in field value:
	*DB does not implement TestDB (wrong type for Query method)
		have Query(string) (*Rows, error)
		want Query(string) (TestRows, error)

你可以尝试将 *DB 类型转换为 TestDB 类型,以满足接口的要求。

英文:

How to correctly mock some <code>*sql.DB</code> methods for unit testing using interfaces? For example, I want to have a <code>Store</code> struct that is used as a datasource for my handlers. It has a <code>conn</code> field that is the <code>*sql.DB</code>. But in my tests I want to mock the DB, so I try to return not the actual <code>*sql.Rows</code> but an interface that the <code>*sql.Rows</code> should satisfy. Here is the code:

type TestRows interface {
    Scan ()
}

type TestDB interface {
    Query (query string, args ...interface{}) (TestRows, error) 
}

type Rows struct {
    //..
}

func (r *Rows) Scan () {
    fmt.Println(&quot;Scanning&quot;)
}

This is the <code>*sql.DB</code>

type DB struct {
    //...
}

func (d *DB) Query (query string, args ...inteface{}) (*Rows, error) {
    return &amp;Rows{/* .... */}, nil
}

And when I want to instantiate the <code>Store</code>, it throws error

type Store struct {
    conn TestDB
}

func main() { 
    cl := Store {conn: &amp;DB{}}
    fmt.Println(&quot;Hello, playground&quot;)
}

cannot use DB literal (type *DB) as type TestDB in field value:
	*DB does not implement TestDB (wrong type for Query method)
    	have Query(string) (*Rows, error)
	    want Query(string) (TestRows, error)

答案1

得分: 1

我建议不要嘲笑数据库,这样做是一个不好的主意。你可以验证查询是否使用了特定的SQL语句,并返回虚假结果,但这真的没有什么用处。这些测试增加了很少的价值,并给人一种虚假的信心。你的测试接受什么与实际在数据库中运行的内容之间没有任何联系。

真正的危险之一是,你的数据库模式可能会发生变化,但你的模拟没有更新。然后你的代码在生产环境中会失败,但测试通过。

相反,实现一种数据抽象层,比如数据访问对象(DAO)。

你可以编写自动集成测试来测试你的DAO与数据库的交互。这将确保你的DAO实际上是有效的,并捕捉到任何错误,比如在模式更改后没有更新查询。

然后,你可以为其他代码编写单元测试。与数据库交互的任何内容都应该使用一个模拟/存根/虚拟的DAO,具体取决于测试的要求。

英文:

I would suggest that it's a bad idea to mock the DB. You can verify that queries are being called with a particular SQL string and return fake results but this really isn't useful. These tests add little value and install a false sense of confidence. There's absolutely no link between what your test accepts and what will actually run in your DB.

One of the real dangers is that your database schema could change but your mocks aren't updated. Then your code will fail in production but pass the tests.

Instead, implement some sort of data abstraction layer, e.g. like a Data Access Object (DAO).

You can write automatic integration tests that will test your DAO against your database. This will ensure that your DAO actually works and catch any errors like a query not being updated after a schema change.

You can then write unit tests for your other code. Anything that interacts with the database should use a mock/stub/fake DAO depending on what the test requires.

答案2

得分: 0

Query函数的定义需要修改为:

func (d *DB) Query(query string, args ...interface{}) (TestRows, error) {
    return &Rows{count: 1}, nil
}

编辑:

如果如你所指出的,你不能修改Query函数的实现,因为你需要满足其他接口的要求,那么你需要调整自己的接口。在这种情况下,可以这样修改:

type TestDB interface {
    Query(query string, args ...interface{}) (*Rows, error)
}
英文:

The definition of Query function needs to be:

func (d *DB) Query(query string, args ...interface{}) (TestRows, error) {
    return &amp;Rows{count: 1}, nil
}

Edit:

If as -- you pointed out -- you can't change the implementation of the Query function because you need to meet other interfaces, then you need to adjust your own interfaces. In this case

type TestDB interface {
    Query(query string, args ...interface{}) (*Rows, error)
}

huangapple
  • 本文由 发表于 2017年8月10日 01:03:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/45596695.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定