Go 单位测试之Mysql数据库集成测试

嚴華  金牌会员 | 2024-5-17 15:57:38 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 919|帖子 919|积分 2757

目录

一、 sqlmock先容

sqlmock 是一个用于测试数据库交互的 Go 模拟库。它可以模拟 SQL 查询、插入、更新等操作,并且可以验证 SQL 语句的实行环境,非常得当用于单位测试中。
二、安装
  1. go get github.com/DATA-DOG/go-sqlmock
复制代码
三、基本用法

使用 sqlmock 举行 MySQL 数据库集成测试的基本步骤如下:

  • 创建模拟 DB 毗连:
  1. import (
  2.     "database/sql"
  3.     "testing"
  4.     "github.com/DATA-DOG/go-sqlmock"
  5. )
  6. func TestMyDBFunction(t *testing.T) {
  7.     db, mock, err := sqlmock.New()
  8.     if err != nil {
  9.         t.Fatalf("Error creating mock database: %v", err)
  10.     }
  11.     defer db.Close()
  12.    
  13.     // 使用 mock 来替代真实的数据库连接
  14.     // db 可以传递给被测试的函数进行测试
  15. }
复制代码

  • 设置模拟 SQL 查询和预期效果:
  1. // 模拟 SQL 查询并设置预期结果
  2. rows := sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "Alice").AddRow(2, "Bob")
  3. mock.ExpectQuery("SELECT id, name FROM users").WillReturnRows(rows)
复制代码

  • 调用被测试的函数,并传入模拟的数据库毗连:
  1. // 调用被测试的函数,传入模拟的数据库连接
  2. result := MyDBFunction(db)
  3. // 验证结果是否符合预期
  4. if result != expected {
  5.     t.Errorf("Expected %d, got %d", expected, result)
  6. }
复制代码
四、一个小案例

这里我们定义了一个 GORMUserDAO 结构体,它实现了 UserDAO 接口,用于与用户表举行交互。这个结构体通过 gorm.DB 实例与数据库举行通信。
具体来说,GORMUserDAO 提供了 Insert 方法,用于在数据库中创建新用户。这个方法担当一个 User 类型的结构体作为参数,该结构体定义了用户的基本信息,包括 ID、邮箱、暗码、手机号、生日、昵称、自我先容、微信 UnionID 和 OpenID 等字段。
在 Insert 方法中,起首获取当前时间戳(以毫秒为单位),并设置用户的创建时间和更新时间。然后,使用 gorm.DB 的 Create 方法将用户信息插入到数据库中。如果插入操作碰到唯一性约束错误(例如邮箱或手机号已存在),方法会返回一个特定的错误 ErrUserDuplicate。
User 结构体定义了数据库表的结构,其中包含了一些列的定义,如 Email 和 Phone 被设置为唯一索引。此外,还定义了一些列的类型和约束,如 AboutMe 字段被设置为最大长度为 1024 的字符串类型。
提供了一个使用 GORM 举行数据库操作的 DAO 层,用于处置惩罚用户数据的创建。
  1. // internal/user/dao/user.go
  2. package dao
  3. import (
  4.         "context"
  5.         "database/sql"
  6.         "errors"
  7.         "github.com/go-sql-driver/mysql"
  8.         "gorm.io/gorm"
  9.         "time"
  10. )
  11. var (
  12.         ErrUserDuplicate = errors.New("邮箱冲突")
  13. )
  14. type UserDAO interface {
  15.         Insert(ctx context.Context, u User) error
  16. }
  17. type GORMUserDAO struct {
  18.         db *gorm.DB
  19. }
  20. func NewUserDAO(db *gorm.DB) UserDAO {
  21.         return &GORMUserDAO{
  22.                 db: db,
  23.         }
  24. }
  25. func (dao *GORMUserDAO) Insert(ctx context.Context, u User) error {
  26.         // 存毫秒数
  27.         now := time.Now().UnixMilli()
  28.         u.Utime = now
  29.         u.Ctime = now
  30.         err := dao.db.WithContext(ctx).Create(&u).Error
  31.         if mysqlErr, ok := err.(*mysql.MySQLError); ok {
  32.                 const uniqueConflictsErrNo uint16 = 1062
  33.                 if mysqlErr.Number == uniqueConflictsErrNo {
  34.                         // 邮箱冲突 or 手机号码冲突
  35.                         return ErrUserDuplicate
  36.                 }
  37.         }
  38.         return err
  39. }
  40. // User 直接对应数据库表结构
  41. // 有些人叫做 entity,有些人叫做 model,有些人叫做 PO(persistent object)
  42. type User struct {
  43.         Id int64 `gorm:"primaryKey,autoIncrement"`
  44.         // 设置为唯一索引
  45.         Email    sql.NullString `gorm:"unique"`
  46.         Password string
  47.         //Phone *string
  48.         Phone sql.NullString `gorm:"unique"`
  49.         Birthday sql.NullInt64
  50.         // 昵称
  51.         Nickname sql.NullString
  52.         // 自我介绍
  53.         AboutMe       sql.NullString `gorm:"type=varchar(1024)"`
  54.         WechatUnionID sql.NullString
  55.         WechatOpenID  sql.NullString `gorm:"unique"`
  56.         // 创建时间
  57.         Ctime int64
  58.         // 更新时间
  59.         Utime int64
  60. }
复制代码
接着我们用编写测试用例
  1. package dao
  2. import (
  3.         "context"
  4.         "database/sql"
  5.         "errors"
  6.         "github.com/DATA-DOG/go-sqlmock"
  7.         "github.com/go-sql-driver/mysql"
  8.         "github.com/stretchr/testify/assert"
  9.         "github.com/stretchr/testify/require"
  10.         gormMysql "gorm.io/driver/mysql"
  11.         "gorm.io/gorm"
  12.         "testing"
  13. )
  14. func TestGORMUserDAO_Insert(t *testing.T) {
  15.         //
  16.         testCases := []struct {
  17.                 name string
  18.                 // 为什么不用 ctrl ?
  19.                 // 因为你这里是 sqlmock,不是 gomock
  20.                 mock func(t *testing.T) *sql.DB
  21.                 ctx  context.Context
  22.                 user User
  23.                 wantErr error
  24.         }{
  25.                 {
  26.                         name: "插入成功",
  27.                         mock: func(t *testing.T) *sql.DB {
  28.                                 mockDB, mock, err := sqlmock.New()
  29.                                 res := sqlmock.NewResult(3, 1)
  30.                                 // 这边预期的是正则表达式
  31.                                 // 这个写法的意思就是,只要是 INSERT 到 users 的语句
  32.                                 mock.ExpectExec("INSERT INTO `users` .*").
  33.                                         WillReturnResult(res)
  34.                                 require.NoError(t, err)
  35.                                 return mockDB
  36.                         },
  37.                         user: User{
  38.                                 Email: sql.NullString{
  39.                                         String: "123@qq.com",
  40.                                         Valid:  true,
  41.                                 },
  42.                         },
  43.                 },
  44.                 {
  45.                         name: "邮箱冲突",
  46.                         mock: func(t *testing.T) *sql.DB {
  47.                                 mockDB, mock, err := sqlmock.New()
  48.                                 // 这边预期的是正则表达式
  49.                                 // 这个写法的意思就是,只要是 INSERT 到 users 的语句
  50.                                 mock.ExpectExec("INSERT INTO `users` .*").
  51.                                         WillReturnError(&mysql.MySQLError{
  52.                                                 Number: 1062,
  53.                                         })
  54.                                 require.NoError(t, err)
  55.                                 return mockDB
  56.                         },
  57.                         user:    User{},
  58.                         wantErr: ErrUserDuplicate,
  59.                 },
  60.                 {
  61.                         name: "数据库错误",
  62.                         mock: func(t *testing.T) *sql.DB {
  63.                                 mockDB, mock, err := sqlmock.New()
  64.                                 // 这边预期的是正则表达式
  65.                                 // 这个写法的意思就是,只要是 INSERT 到 users 的语句
  66.                                 mock.ExpectExec("INSERT INTO `users` .*").
  67.                                         WillReturnError(errors.New("数据库错误"))
  68.                                 require.NoError(t, err)
  69.                                 return mockDB
  70.                         },
  71.                         user:    User{},
  72.                         wantErr: errors.New("数据库错误"),
  73.                 },
  74.         }
  75.         for _, tc := range testCases {
  76.                 t.Run(tc.name, func(t *testing.T) {
  77.                         db, err := gorm.Open(gormMysql.New(gormMysql.Config{
  78.                                 Conn: tc.mock(t),
  79.                                 // SELECT VERSION;
  80.                                 SkipInitializeWithVersion: true,
  81.                         }), &gorm.Config{
  82.                                 // 你 mock DB 不需要 ping
  83.                                 DisableAutomaticPing: true,
  84.                                 // 这个是什么呢?
  85.                                 SkipDefaultTransaction: true,
  86.                         })
  87.                         d := NewUserDAO(db)
  88.                         u := tc.user
  89.                         err = d.Insert(tc.ctx, u)
  90.                         assert.Equal(t, tc.wantErr, err)
  91.                 })
  92.         }
  93. }
复制代码
五、Gorm 初始化留意点

这里运行测试的代码也有点与众不同,在初始化 GORM 的时候必要额外设置三个参数。

  • SkipInitializeWithVersion:如果为 false,那么 GORM 在初始化的时候,会先调用 show version。
  • DisableAutomiticPing:为 true 不允许 Ping 数据库。
  • SkipDefaultTransaction:为 false 的时候,即便是一个单一增删改语句,GORM 也会开启事务。
这三个选项禁用之后,就可以确保 GORM 不会在初始化的过程中发起额外的调用。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

嚴華

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表