本文首发于:科学世纪的炼金工坊​yuchanns.org

今天q群一哥们儿说,他使用beego orm的InserOrUpdate的时候出现了相同主键还是会执行新增插入的bug,找我帮忙看看什么情况。

当时我的第一反应是让他先在debug模式下打印sql语句看看有没有什么问题,但小伙子可能是比较紧张一直打印不出来。

由于我当时不在生产电脑前,对beego也不是很熟悉,只能临时用普通电脑装一个go,设置一下环境拉一下代码写一个测试用例。因为安装mysql太麻烦了,所以我打算简单的用DATA-DOG/go-sqlmock来mock数据库返回。

于是就顺手写一下使用记录,算是给那位大兄弟的一个教程科普吧。

情景简述

案例情景介绍如下:有一个TExchangeInfo结构体,实例化后填充数据,然后执行InsertOrUpdate,当数据存在时,使用更新,当数据不存在时才插入:

type TExchangeInfo struct {

ID int64 `orm:"column(id);auto"`

DeparmentID int64 `orm:"column(deparment_id)"`

Times uint `orm:"column(times)"`

Number uint `orm:"column(number)"`

Lastmodified time.Time `orm:"column(lastmodified);type(datetime);auto_now"`

}

sqlmock使用

sqlmock的使用其实很简单,参照文档就可以。我这里简单说明一下。

首先大家都知道,go标准库有一个datebase/sql/driver包,内部定义了数据库驱动标准接口,不管什么方言的数据库,只要实现了这些接口,就可以统一调用接口定制的方法来进行数据库交互。

而sqlmock也是通过sqlmock.New()这个方法返回一个标准的sql.DB结构体实例指针,这是一个数据库连接句柄。当然除此之外还返回了一个sqlmock.Sqlmock结构体实例。

而我们拿到*sql.DB之后,就可以递交给orm来使用了。

以beego orm为例,它有一个orm.NewOrmWithDB方法,用来实例化并指定连接句柄。

func InsertOrUpdatePrintSql() error {

db, mock, err := sqlmock.New()

if err != nil {

return err

}

defer db.Close()

orm.Debug = true // 开启debug模式才能打印出拼装的sql语句 o, err := orm.NewOrmWithDB("mysql", "default", db)

if err != nil {

return err

}

}

写到这里,似乎我们已经能够和往常一样使用orm了。试着写一个测试用例运行这个函数,结果会发现报错了,一个panic:

panic: all expectations were already fulfilled, call to Prepare 'SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)' query was not expected [recovered]

一时之间令人摸不着头脑?这和接下来我们要讲的sqlmock.Sqlmock有关。

mock数据

mock的核心就在于mock这个词,也就是说,屏蔽上游细节,使用一些实现设定好的数据来模拟上游返回的数据。

sqlmock也同样如此,你需要在mock测试过程中,指定你期望(Expectations)执行的查询语句,以及假定的返回结果(WillReturnResult)。注:beego orm在启动时候,会先执行SELECT TIMEDIFF...和SELECT ENGINE...两个语句,所以我们也需要把它添加到我们的期望中。

func InsertOrUpdatePrintSql() error {

db, mock, err := sqlmock.New()

if err != nil {

return err

}

defer db.Close()

// ExpectPrepare,期望执行一条Prepare语句 mock.ExpectPrepare("SELECT TIMEDIFF")

mock.ExpectPrepare("SELECT ENGINE")

// ExpectExec,期望执行一条Exec语句 // 然后假定会返回(1, 1),也就是自增主键为1,1条影响结果 mock.ExpectExec("INSERT").

WillReturnResult(sqlmock.NewResult(1, 1))

orm.Debug = true

o, err := orm.NewOrmWithDB("mysql", "default", db)

if err != nil {

return err

}

_ = o.Using("db1")

// beego要求需要先注册结构体 orm.RegisterModel(new(TExchangeInfo))

u := &TExchangeInfo{

ID: 10086,

DeparmentID: 1,

Times: 0,

Number: 10,

}

_, err = o.InsertOrUpdate(u)

return err

}

添加你的期望,然后执行orm动作。接着我们在标准输出口看到打印出来的sql语句

=== RUN TestInsertOrUpdatePrintSql

[ORM]2020/09/16 23:43:39 -[Queries/default] - [ OK / db.Exec / 0.1ms] - [INSERT INTO `t_exchange_info` (`deparment_id`, `times`, `number`, `lastmodified`) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE `deparment_id`=?, `times`=?, `number`=?, `lastmodified`=?] - `1`, `0`, `10`, `2020-09-16 23:43:39.178543 +0800 CST`, `1`, `0`, `10`, `2020-09-16 23:43:39.178543 +0800 CST`

--- PASS: TestInsertOrUpdatePrintSql (0.00s)

PASS

分析问题

整理一下输出语句,我们发现,beego orm使用的是数据库自身的insert or update功能来实现的新增插入修改更新的交互。但是整条语句中却毫无主键的痕迹——

INSERT INTO `t_exchange_info` (`deparment_id`, `times`, `number`, `lastmodified`) VALUES (`1`, `0`, `10`, `2020-09-16 23:43:39.178543 +0800 CST`) ON DUPLICATE KEY UPDATE `deparment_id`=`1`, `times`=`0`, `number`=`10`, `lastmodified`=`2020-09-16 23:43:39.178543 +0800 CST`

那么我们应该意识到,很可能是beego orm在执行过程中,过滤掉了主键。这难道是个bug吗?

在追溯源码之后,我们判定问题在于github.com/astaxie/beego@v1.12.2/orm/db_mysql.go第122行代码这里。快速使用Goland自带的断点debug功能打一个断点,然后进行单步调试。

最终我们发现真正问题在于在github.com/astaxie/beego@v1.12.2/orm/db.go第91行这里,在结构体字段的tag中包含有auto属性时,会被跳过,这就是造成过滤的原因。

结论

经过咨询得知,那位大兄弟在建立数据库交互所使用的数据结构体时,习惯在主键上打一个autotag,认为这样表示主键自增的意思。

我告诉他,auto标签只是用于告诉框架进行自增操作,属于框架代码层面的操作,而不是数据库层面的操作,并不表示为主键。如果要表示主键,也应该是pk。

去掉auto,问题解决。

go mock mysql_go sqlmocks的使用相关推荐

  1. 属性匹配工具_测试工具链——高效构建Mock服务

    现在,WEB系统的开发一般都采用前后端分离的架构,以及部分公司采用"前台-中台-后台"的组织架构,难免会出现开发进度不一致的情况,导致系统联调或测试需要等到所有依赖开发完成后才能够 ...

  2. RAP Mock.js语法规范

    Mock.js 的语法规范包括两部分: 数据模板定义规范(Data Template Definition,DTD) 数据占位符定义规范(Data Placeholder Definition,DPD ...

  3. Google Mock(Gmock)简单使用和源码分析——源码分析

    源码分析 通过<Google Mock(Gmock)简单使用和源码分析--简单使用>中的例子,我们发现被mock的相关方法在mock类中已经被重新实现了,否则它们也不会按照我们的期待的行为 ...

  4. Google Mock(Gmock)简单使用和源码分析——简单使用

    初识Gmock是之前分析GTest源码时,它的源码和GTest源码在同一个代码仓库中(https://github.com/google/googletest).本文我将以目前最新的Gmock1.7版 ...

  5. python2.7 mysql mock_Python中Mock的示例

    一些常用的mock示例 先简单定义个类,方便举例: class Person: def __init__(self): self.__age = 10 def get_fullname(self, f ...

  6. Django项目test中的mock概述

    Django项目test中的mock概述 本文环境python3.5.2 test中的mock实现 接口示例代码如下: ...# 路由配置('^api/business_application/?$' ...

  7. Mock session,cookie,querystring in ASB.NET MVC

    写测试用例的时候经常发现,所写的功能需要Http上下文的支持(session,cookie)这类的. 以下介绍2种应用场景. 用于控制器内Requet获取参数 控制器内的Requet其实是控制器内的属 ...

  8. 干货!用大白话告诉你什么是Mock测试

    初识mock 作为一个动词,mock是模拟.模仿的意思:作为一个名词,mock是能够模仿真实对象行为的模拟对象. 在软件测试中,mock所模拟的对象是什么呢? 它一定不是我们所测试的对象,而是 SUT ...

  9. SoapUI:mock service的简单使用

    转载自博客园:https://www.cnblogs.com/helenMemery/p/6343493.html mock service就是服务模拟,当我们的接口完成而服务端还没完成的时候,我们就 ...

  10. 1分钟搭建极简mock server

    摘自博客园:https://www.cnblogs.com/mikasama/p/9838480.html 1.无聊的背景.起源: 如今的业务系统越来越复杂庞大,各个功能直接的调用也是多如牛毛,但如果 ...

最新文章

  1. sersync 文件同步系统(一) 服务初步搭建
  2. libevent中的缓冲区(二)
  3. Python 正则表达式 匹配任意字符
  4. 分布式ActiveMQ集群--转载
  5. 最高效的回文数(C语言实现)
  6. 微信获取token -1000
  7. 纯CSS3实现牛奶般剔透的3D按钮特效
  8. Scaled Exponential Linear Unit
  9. 信息安全管理体系--建立
  10. Python下十进制转换为二进制
  11. 【网页版 GitHub】操作指南(搜索、下载等)
  12. Mac App图标制作工具——img2icns
  13. 第10章 基础API与常见算法
  14. 【教学类-12-01】20221105《连连看8*4-分栏4-不重复16个)(小班主题《白天与黑夜》)
  15. [Python]百钱买鸡流程图及程序设计
  16. 再也不见,雅虎被中国市场击败
  17. SQL Server 2014下载,安装和使用教程
  18. 北京市昌平区申通快递 电话
  19. 盐城大数据产业园人才公寓_盐城市大数据产业园大步流星
  20. android滑动一个路线后 人物图片按此路线移动的实现

热门文章

  1. Java异常框架设计
  2. java之j2se:再学java对象容器
  3. jQuery事件委派与移除
  4. CCNP学习之路之QOS配置命令
  5. POJ_2828 Buy Ticket(线段树)
  6. 再续上一篇:如果哪天沃尔玛也“.CN”了
  7. 【Docker】05 容器数据卷
  8. 6sp电池测试软件,上手6SP大容量电池,3550mAh(深度测试篇)
  9. linux recv返回值,Nginx 的recv() failed 错误解决一例
  10. itemCF matlab算法,推荐系统初探:ItemCF算法实现知乎问题推荐