DATA-DOG/go-sqlmock

GitHub: DATA-DOG/go-sqlmock

一个 Go 语言的 SQL mock 库,让开发者在无需真实数据库连接的情况下测试数据库交互逻辑。

Stars: 6563 | Forks: 413

[![构建状态](https://travis-ci.org/DATA-DOG/go-sqlmock.svg)](https://travis-ci.org/DATA-DOG/go-sqlmock) [![GoDoc](https://godoc.org/github.com/DATA-DOG/go-sqlmock?status.svg)](https://godoc.org/github.com/DATA-DOG/go-sqlmock) [![Go Report Card](https://goreportcard.com/badge/github.com/DATA-DOG/go-sqlmock)](https://goreportcard.com/report/github.com/DATA-DOG/go-sqlmock) [![codecov.io](https://codecov.io/github/DATA-DOG/go-sqlmock/branch/master/graph/badge.svg)](https://codecov.io/github/DATA-DOG/go-sqlmock) # Golang 的 SQL driver mock **sqlmock** 是一个实现了 [sql/driver](https://godoc.org/database/sql/driver) 的 mock 库。它有且仅有一个 目的 - 在测试中模拟任何 **sql** driver 的行为,而无需真正的数据库连接。它有助于 维持正确的 **TDD** 工作流程。 - 该库目前已经完整且稳定。(因此你可能不会看到新的变化) - 支持并发和多重连接。 - 支持 **go1.8** Context 相关特性的 mock 以及带命名的 SQL 参数。 - 不需要对你的源代码进行任何修改。 - 该 driver 允许 mock 任何 SQL driver 方法的行为。 - 默认具有严格的期望顺序匹配。 - 没有任何第三方依赖。 **注意:** 在 **v1.2.0** 版本中,**sqlmock.Rows** 从 interface 改为了 struct,如果你之前使用了指向该 interface 的任何类型引用,你需要将其切换为指针 struct 类型。此外,**sqlmock.Rows** 曾被用于实现 **driver.Rows** interface,这对于 mock 来说既不需要也没有用,因此已被移除。希望这不会造成任何问题。 ## 寻找维护者 我没有太多空闲时间来维护这个库,并且愿意将仓库的所有权转移给 有动力维护它的个人或组织。如果你感兴趣,请开启一次对话。参见 #230。 ## 安装 ``` go get github.com/DATA-DOG/go-sqlmock ``` ## 文档与示例 访问 [godoc](http://godoc.org/github.com/DATA-DOG/go-sqlmock) 获取常规示例和公开 API 参考。 请查看 **.travis.yml** 以了解支持的 **go** 版本。 另一种不同的用例是使用真实的数据库进行功能测试 - [go-txdb](https://github.com/DATA-DOG/go-txdb) 所有与数据库相关的操作都被隔离在单个事务中,因此数据库可以保持在相同的状态。 查看实现示例: - [博客 API server](https://github.com/DATA-DOG/go-sqlmock/tree/master/examples/blog) - [相同的订单示例](https://github.com/DATA-DOG/go-sqlmock/tree/master/examples/orders) ### 一些你可能想要测试的内容,假设你使用的是 [go-mysql-driver](https://github.com/go-sql-driver/mysql) ``` package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" ) func recordStats(db *sql.DB, userID, productID int64) (err error) { tx, err := db.Begin() if err != nil { return } defer func() { switch err { case nil: err = tx.Commit() default: tx.Rollback() } }() if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil { return } if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil { return } return } func main() { // @NOTE: the real connection is not required for tests db, err := sql.Open("mysql", "root@/blog") if err != nil { panic(err) } defer db.Close() if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil { panic(err) } } ``` ### 使用 sqlmock 进行测试 ``` package main import ( "fmt" "testing" "github.com/DATA-DOG/go-sqlmock" ) // a successful case func TestShouldUpdateStats(t *testing.T) { 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.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() // now we execute our method if err = recordStats(db, 2, 3); err != nil { t.Errorf("error was not expected while updating stats: %s", err) } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } } // a failing test case func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) { 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.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers"). WithArgs(2, 3). WillReturnError(fmt.Errorf("some error")) mock.ExpectRollback() // now we execute our method if err = recordStats(db, 2, 3); err == nil { t.Errorf("was expecting an error, but there was none") } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } } ``` ## 自定义 SQL 查询匹配 有许多来自用户的请求,希望能对 SQL 查询字符串进行验证或提供不同的匹配选项。 我们现在实现了 `QueryMatcher` interface,它可以在调用 `sqlmock.New` 或 `sqlmock.NewWithDSN` 时通过选项传入。 这现在允许引入一些库,例如用来解析和验证 `mysql` SQL AST。 并创建一个自定义的 QueryMatcher,以便以更复杂的方式验证 SQL。 默认情况下,**sqlmock** 保持了向后兼容性,默认的查询匹配器是 `sqlmock.QueryMatcherRegexp`, 它将期望的 SQL 字符串作为正则表达式来匹配传入的查询字符串。这里还有一个相等匹配器: `QueryMatcherEqual`,它会进行完全区分大小写的匹配。 为了自定义 QueryMatcher,请使用以下代码: ``` db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) ``` 可以根据用户需求完全自定义查询匹配器。**sqlmock** 不会 提供标准的 SQL 解析匹配器,因为各种 driver 可能不会遵循相同的 SQL 标准。 ## 匹配诸如 time.Time 之类的参数 可能存在一些 `struct` 类型的参数,例如 `time.Time`,它们无法轻易通过值进行比较。在这种情况下, **sqlmock** 提供了一个 [Argument](https://godoc.org/github.com/DATA-DOG/go-sqlmock#Argument) interface,可以用于更复杂的匹配。以下是一个时间参数匹配的简单示例: ``` type AnyTime struct{} // Match satisfies sqlmock.Argument interface func (a AnyTime) Match(v driver.Value) bool { _, ok := v.(time.Time) return ok } func TestAnyTimeArgument(t *testing.T) { t.Parallel() db, mock, err := sqlmock.New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectExec("INSERT INTO users"). WithArgs("john", AnyTime{}). WillReturnResult(sqlmock.NewResult(1, 1)) _, err = db.Exec("INSERT INTO users(name, created_at) VALUES (?, ?)", "john", time.Now()) if err != nil { t.Errorf("error '%s' was not expected, while inserting a row", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } } ``` 它只会断言参数是否属于 `time.Time` 类型。 ## 运行测试 ``` go test -race ``` ## 更新日志 - **2019-04-06** - 增加了对 SQL MetaData 请求进行 mock 的功能 - **2019-02-13** - 添加了 `go.mod`,移除了对使用 `gopkg.in` 的引用和建议。 - **2018-12-11** - 在 mock 预期查询时,增加了对 Rows 被关闭的期望。 - **2018-12-14** - 引入了一个提供 **QueryMatcher** 的选项,以便自定义 SQL 查询匹配。 - **2017-09-01** - 现在可以期望 prepared statement 将被关闭, 使用 **ExpectedPrepare.WillBeClosed**。 - **2017-02-09** - 实现了对 **go1.8** 特性的支持。**Rows** interface 被改为 struct, 但包含了以前的所有方法,并且应该保持向后兼容。**ExpectedQuery.WillReturnRows** 现在 可以接受多个行集。 - **2016-11-02** - `db.Prepare()` 没有验证预期的 prepare SQL 查询。即使没有在该 prepared statement 上执行 Exec 或 Query,它仍然应该被验证。 - **2016-02-23** - 添加了 **sqlmock.AnyArg()** 函数,以提供任何类型的 参数匹配器。 - **2016-02-23** - 将预期的参数转换为 driver.Value,就像原生的 driver 那样,此更改可能会影响 time.Time 的比较,并且会更加 严格。参见 [issue](https://github.com/DATA-DOG/go-sqlmock/issues/31)。 - **2015-08-27** - **v1** API 变更,支持并发,修复了所有已知问题。 - **2014-08-16** - 在比较查询参数时,如果发生 reflect 类型不匹配,现在会返回 error 而不是引发 **panic** - **2014-08-14** - 添加了 **sqlmock.NewErrorResult**,它提供了一个选项,可以针对 interface 方法返回带有错误的 driver.Result,参见 [issue](https://github.com/DATA-DOG/go-sqlmock/issues/5) - **2014-05-29** - 允许通过提供 **sqlmock.Argument** interface,以更复杂的方式匹配参数 - **2014-04-21** - 引入 **sqlmock.New()** 来为测试打开一个 mock 数据库连接。该方法 调用 sql.DB.Ping 以确保连接已打开,参见 [issue](https://github.com/DATA-DOG/go-sqlmock/issues/4)。 这样在 Close 时,它必定能断言是否满足了所有期望,即使数据库根本没有被触发。 旧的方法仍然可用,但建议在使用 db.Close 进行断言之前手动调用 db.Ping。 - **2014-02-14** RowsFromCSVString 现在是 Rows interface 的一部分,名为 FromCSVString。 它被更改是为了允许以更多方式构造行,并方便在未来扩展此 API。 参见 [issue 1](https://github.com/DATA-DOG/go-sqlmock/issues/1) **RowsFromCSVString** 已被弃用,并将在未来被移除 ## 贡献 随时欢迎提交 pull request。请注意,如果你希望对公开内容(导出的方法或类型)进行扩展贡献 - 请先开启一个 issue,讨论这些更改是否可以被接受。所有向后不兼容的更改 都会并且将被谨慎处理 ## 许可证 [三条款 BSD 许可证](http://en.wikipedia.org/wiki/BSD_licenses)
标签:EVTX分析, 日志审计