目录
一、 sqlmock先容
sqlmock 是一个用于测试数据库交互的 Go 模拟库。它可以模拟 SQL 查询、插入、更新等操作,并且可以验证 SQL 语句的实行环境,非常得当用于单位测试中。
二、安装
- go get github.com/DATA-DOG/go-sqlmock
复制代码 三、基本用法
使用 sqlmock 举行 MySQL 数据库集成测试的基本步骤如下:
- import (
- "database/sql"
- "testing"
- "github.com/DATA-DOG/go-sqlmock"
- )
- func TestMyDBFunction(t *testing.T) {
- db, mock, err := sqlmock.New()
- if err != nil {
- t.Fatalf("Error creating mock database: %v", err)
- }
- defer db.Close()
-
- // 使用 mock 来替代真实的数据库连接
- // db 可以传递给被测试的函数进行测试
- }
复制代码- // 模拟 SQL 查询并设置预期结果
- rows := sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "Alice").AddRow(2, "Bob")
- mock.ExpectQuery("SELECT id, name FROM users").WillReturnRows(rows)
复制代码- // 调用被测试的函数,传入模拟的数据库连接
- result := MyDBFunction(db)
- // 验证结果是否符合预期
- if result != expected {
- t.Errorf("Expected %d, got %d", expected, result)
- }
复制代码 四、一个小案例
这里我们定义了一个 GORMUserDAO 结构体,它实现了 UserDAO 接口,用于与用户表举行交互。这个结构体通过 gorm.DB 实例与数据库举行通信。
具体来说,GORMUserDAO 提供了 Insert 方法,用于在数据库中创建新用户。这个方法担当一个 User 类型的结构体作为参数,该结构体定义了用户的基本信息,包括 ID、邮箱、暗码、手机号、生日、昵称、自我先容、微信 UnionID 和 OpenID 等字段。
在 Insert 方法中,起首获取当前时间戳(以毫秒为单位),并设置用户的创建时间和更新时间。然后,使用 gorm.DB 的 Create 方法将用户信息插入到数据库中。如果插入操作碰到唯一性约束错误(例如邮箱或手机号已存在),方法会返回一个特定的错误 ErrUserDuplicate。
User 结构体定义了数据库表的结构,其中包含了一些列的定义,如 Email 和 Phone 被设置为唯一索引。此外,还定义了一些列的类型和约束,如 AboutMe 字段被设置为最大长度为 1024 的字符串类型。
提供了一个使用 GORM 举行数据库操作的 DAO 层,用于处置惩罚用户数据的创建。- // internal/user/dao/user.go
- package dao
- import (
- "context"
- "database/sql"
- "errors"
- "github.com/go-sql-driver/mysql"
- "gorm.io/gorm"
- "time"
- )
- var (
- ErrUserDuplicate = errors.New("邮箱冲突")
- )
- type UserDAO interface {
- Insert(ctx context.Context, u User) error
- }
- type GORMUserDAO struct {
- db *gorm.DB
- }
- func NewUserDAO(db *gorm.DB) UserDAO {
- return &GORMUserDAO{
- db: db,
- }
- }
- func (dao *GORMUserDAO) Insert(ctx context.Context, u User) error {
- // 存毫秒数
- now := time.Now().UnixMilli()
- u.Utime = now
- u.Ctime = now
- err := dao.db.WithContext(ctx).Create(&u).Error
- if mysqlErr, ok := err.(*mysql.MySQLError); ok {
- const uniqueConflictsErrNo uint16 = 1062
- if mysqlErr.Number == uniqueConflictsErrNo {
- // 邮箱冲突 or 手机号码冲突
- return ErrUserDuplicate
- }
- }
- return err
- }
- // User 直接对应数据库表结构
- // 有些人叫做 entity,有些人叫做 model,有些人叫做 PO(persistent object)
- type User struct {
- Id int64 `gorm:"primaryKey,autoIncrement"`
- // 设置为唯一索引
- Email sql.NullString `gorm:"unique"`
- Password string
- //Phone *string
- Phone sql.NullString `gorm:"unique"`
- Birthday sql.NullInt64
- // 昵称
- Nickname sql.NullString
- // 自我介绍
- AboutMe sql.NullString `gorm:"type=varchar(1024)"`
- WechatUnionID sql.NullString
- WechatOpenID sql.NullString `gorm:"unique"`
- // 创建时间
- Ctime int64
- // 更新时间
- Utime int64
- }
复制代码 接着我们用编写测试用例- package dao
- import (
- "context"
- "database/sql"
- "errors"
- "github.com/DATA-DOG/go-sqlmock"
- "github.com/go-sql-driver/mysql"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- gormMysql "gorm.io/driver/mysql"
- "gorm.io/gorm"
- "testing"
- )
- func TestGORMUserDAO_Insert(t *testing.T) {
- //
- testCases := []struct {
- name string
- // 为什么不用 ctrl ?
- // 因为你这里是 sqlmock,不是 gomock
- mock func(t *testing.T) *sql.DB
- ctx context.Context
- user User
- wantErr error
- }{
- {
- name: "插入成功",
- mock: func(t *testing.T) *sql.DB {
- mockDB, mock, err := sqlmock.New()
- res := sqlmock.NewResult(3, 1)
- // 这边预期的是正则表达式
- // 这个写法的意思就是,只要是 INSERT 到 users 的语句
- mock.ExpectExec("INSERT INTO `users` .*").
- WillReturnResult(res)
- require.NoError(t, err)
- return mockDB
- },
- user: User{
- Email: sql.NullString{
- String: "123@qq.com",
- Valid: true,
- },
- },
- },
- {
- name: "邮箱冲突",
- mock: func(t *testing.T) *sql.DB {
- mockDB, mock, err := sqlmock.New()
- // 这边预期的是正则表达式
- // 这个写法的意思就是,只要是 INSERT 到 users 的语句
- mock.ExpectExec("INSERT INTO `users` .*").
- WillReturnError(&mysql.MySQLError{
- Number: 1062,
- })
- require.NoError(t, err)
- return mockDB
- },
- user: User{},
- wantErr: ErrUserDuplicate,
- },
- {
- name: "数据库错误",
- mock: func(t *testing.T) *sql.DB {
- mockDB, mock, err := sqlmock.New()
- // 这边预期的是正则表达式
- // 这个写法的意思就是,只要是 INSERT 到 users 的语句
- mock.ExpectExec("INSERT INTO `users` .*").
- WillReturnError(errors.New("数据库错误"))
- require.NoError(t, err)
- return mockDB
- },
- user: User{},
- wantErr: errors.New("数据库错误"),
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- db, err := gorm.Open(gormMysql.New(gormMysql.Config{
- Conn: tc.mock(t),
- // SELECT VERSION;
- SkipInitializeWithVersion: true,
- }), &gorm.Config{
- // 你 mock DB 不需要 ping
- DisableAutomaticPing: true,
- // 这个是什么呢?
- SkipDefaultTransaction: true,
- })
- d := NewUserDAO(db)
- u := tc.user
- err = d.Insert(tc.ctx, u)
- assert.Equal(t, tc.wantErr, err)
- })
- }
- }
复制代码 五、Gorm 初始化留意点
这里运行测试的代码也有点与众不同,在初始化 GORM 的时候必要额外设置三个参数。
- SkipInitializeWithVersion:如果为 false,那么 GORM 在初始化的时候,会先调用 show version。
- DisableAutomiticPing:为 true 不允许 Ping 数据库。
- SkipDefaultTransaction:为 false 的时候,即便是一个单一增删改语句,GORM 也会开启事务。
这三个选项禁用之后,就可以确保 GORM 不会在初始化的过程中发起额外的调用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |