DATA-DOG/go-sqlmock
GitHub: DATA-DOG/go-sqlmock
一个 Go 语言的 SQL mock 库,让开发者在无需真实数据库连接的情况下测试数据库交互逻辑。
Stars: 6563 | Forks: 413
[](https://travis-ci.org/DATA-DOG/go-sqlmock)
[](https://godoc.org/github.com/DATA-DOG/go-sqlmock)
[](https://goreportcard.com/report/github.com/DATA-DOG/go-sqlmock)
[](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分析, 日志审计