当前位置: 首页 > 科技观察

如何在项目中对GORM做单元测试

时间:2023-03-14 18:32:37 科技观察

前言在实际开发场景中,我们的项目一般都是使用ORM来代替原生的database/sql来完成数据库操作。在很多使用ORM工具的场景下,也可以使用go-sqlmock库来Mock数据库操作进行测试。今天以GORM为例,讲解如何在项目中对ORM数据库操作进行单元测试。项目准备为了场景足够逼真,我将使用我在2020年更新的《GoWeb编程入门》项目中的例子来展示如何使用GORM对DAO层逻辑进行mock测试。这里使用的GORM版本是1.x,可能与2.x版本不兼容。在这个例子中,我们有一个包含用户的表:varchar(1000)"`CreatedAttime.Time`gorm:"column:created_at"`UpdatedAttime.Time`gorm:"column:updated_at"`}func(m*User)TableName()string{return"users"}和几个使用User的DAO函数:var_DB*gorm.DBfuncDB()*gorm.DB{return_DB}funcinit(){//这里省略逻辑,就是初始化GORM的DB对象,//设置连接数据库Configuration//真正的代码可以公众号回复【gohttp15】get_DB=initDB()}funcCreateUser(user*table.User)(errerror){err=DB().Create(user).Errorreturn}funcGetUserByNameAndPassword(name,passwordstring)(user*table.User,errerror){user=new(table.User)err=DB().Where("username=?ANDsecret=?",name,password).First(&user).Errorreturn}funcUpdateUserNameById(userNamestring,userIdint64)(errerror){user:=new(table.User)更新:=map[string]interface{}{"username":userName,}err=DB().Model(user).Where("id=?",userId).Updates(updated).Errorreturn}下面我们先用go-sqlmock工具对这些DAO函数做一个Mock测试初始化??测试总之,我们需要做测试的初始化,主要是设置Mock的DB连接,因为我们需要对三个方法进行Mock测试,最简单的方法就是每隔一段时间初始化Mock的DB连接三个方法中的时间,但是这样做似乎有点傻,这里再给大家一个小技巧。Go的测试支持在包中先执行一个TestMain(m*testing.M)函数,在这里可以对包下的所有测试做一些初始化工作。这是我们为此测试所做的初始化。//私信公众号「网管叮咚」//gohttp15获取源码var(mocksqlmock.Sqlmockerrerrordb*sql.DB)//TestMain是当前运行的第一个函数package,常用用于初始化funcTestMain(m*testing.M){//设置matcher为相等匹配器,不设置则默认使用正则匹配db,mock,err=sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))iferr!=nil{panic(err)}_DB,err=gorm.Open("mysql",db)//m.Run是调用包os.Exit(下的各个Test函数的入口m.Run())}在这个初始化函数中,我们创建一个sqlmock数据库连接db和mock对象,mock对象管理db期望执行的sql。让sqlmock使用QueryMatcherEqual匹配器,它将mock.ExpectQuery和mock.ExpectExec的参数作为预期要执行的SQL语句与实际要执行的SQL进行比较。m.Run是调用包下各个Test函数的入口。准备工作做好了,下面正式进行DAO运行的mock测试。CreateFirst的模拟测试,GORM的Create方法的模拟测试。//私信公众号「网管叮咚」//gohttp15获取源码funcTestCreateUserMock(t*testing.T){user:=&table.User{UserName:"Kevin",Secret:"123456",CreatedAt:time.Now(),UpdatedAt:time.Now(),}mock.ExpectBegin()mock.ExpectExec("INSERTINTO`users`(`username`,`secret`,`created_at`,`updated_at`)VALUES(?,?,?,?)").WithArgs(user.UserName,user.Secret,user.CreatedAt,user.UpdatedAt).WillReturnResult(sqlmock.NewResult(1,1))mock.ExpectCommit()err:=CreateUser(user)assert.Nil(t,err)}因为sqlmock使用了QueryMatcherEqual匹配器,期望执行的SQL语句必须与要执行的SQL完全匹配(包括符号和空格)。如何得到这个SQL?实际上,我们首先编写一个随机SQL并执行一个测试。在错误信息中,我们会告诉CreateUserGORM在写表时实际会执行的SQL。另一种方式是通过GORM提供的Debug()方法获取。例如,在设置了Debug()的情况下运行以下用户创建操作,GORM将打印出执行的语句。funcCreateUser(user*table.User)(errerror){//打印出要执行的SQL语句,记得改回来err=DB().Debug().Create(user).Error//err=DB().Create(user).Errorreturn}我们执行这个测试gotest-v-runTestCreateUserMock--------===RUNTestCreateUserMock---PASS:TestCreateUserMock(0.00s)PASSokgolang-unit-test-demo/sqlmock_gorm_demo0.301s可以看到测试函数执行成功。我们也可以刻意修正SQL,做反向测试。这个留给自己实践,结合上表测试,分别做正向和反向单元测试。Get操作的Mock测试GORM查询操作的Mock测试与Create类似。//私信公众号「网管吟bi唯」//gohttp15获取源码funcTestGetUserByNameAndPasswordMock(t*testing.T){user:=&User{id:1,UserName:"Kevin",秘密:“123456”,创建时间:time.Now(),更新时间:time.Now(),}mock.ExpectQuery(“SELECT*FROM`users`WHERE(username=?ANDsecret=?)"+"ORDERBY`users`.`id`ASCLIMIT1").WithArgs(user.UserName,user.Secret).WillReturnRows(//这里必须匹配结果集中包含的列,因为查询是SELECT*所以必须列出表的字段sqlmock.NewRows([]string{"id","username","secret","created_at","updated_at"}).AddRow(1,user.UserName,user.Secret,user.CreatedAt,user.UpdatedAt))res,err:=GetUserByNameAndPassword(用户.UserName,user.Secret)assert.Nil(t,err)assert.Equal(t,user,res)}这里就不跑文章里的demo了。有兴趣的可以把代码拿下来自己试试。Update操作的Mock测试了GORM的Update操作。我没有测试成功。funcTestUpdateUserNameByIdMock(t*testing.T){newName:="Kev"varuserIdint64=1mock.ExpectBegin()mock.ExpectExec("UPDATE`users`SET`updated_at`=?,`username`=?WHERE(id=?)").WithArgs(time.Now(),newName,userId).WillReturnResult(sqlmock.NewResult(1,1))mock.ExpectCommit()err:=UpdateUserNameById(newName,userId)assert.Nil(t,err)}运行测试后会出现如下错误信息:ExecQuery'UPDATE`users`SET`updated_at`=?,`username`=?WHERE(id=?)',参数不匹配:预期参数0[time.Time-2022-05-0818:13:08.23323+0800CSTm=+0.003082084]与实际[time.Time-2022-05-0818:13:08.234134+0800CSTm=+0.003986334]GORM在UPDATE的时候会自动更新updated_at字段为当前时间,与这里withArgs传递的time.Now()参数不一致(毫秒的差距是不可接受的)。目前没有办法Mock测试GORM的UPDATE,除非使用GORM的Exec方法直接执行要更新的SQL,但是那样就失去了使用ORM的意义,先跳过这个,有经验的在这个领域,你可以留言让我指导你。总结这篇文章,我们已经讲解了ORM的Mock测试。这也是我自己在学习Go单元测试时的思考。希望所学的技能能用在实际项目中。因为文中的例子是基于我之前的GoWeb编程教程在项目中做的测试,所以我也打包更新了GoWeb编程项目的源码,大家可以私信gohttp15获取公众号.如果觉得有用,可以点赞、观看,分享给更多的人。感谢您的支持。我会与时俱进,再次介绍Go1.18Fuzing测试的使用。