golang 读取文件最后一行_测试用例是开发人员最后一块遮羞布

5dde59cc8cbb3c744911d3349088b55b.gif

测试用例是开发人员最后一块遮羞布

最近一周写一个比较复杂的业务模块,越写到后面真心越心虚。操作越来越复杂了,代码也逐渐凌乱了起来。比如一个接口,传入的是一个比较复杂的大json,我需要解析这个大json,然后根据json中字段进行增删改查,调用第三方服务等操作。告诉前端接口已经完成的时候,总是有点没有底气。说实话,在写PHP的时候,我确实很少写单元测试,大都是对着页面进行一波一波的测试,现在想想,一个是懒,还有一个是确实PHP是不需要编译的语言,没有编译时间,测试-修正,整个流程非常短。但是这次是一个比较大的GoLang项目,如果还是按照“编译-起服务-调用-调整代码-编译-起服务-调用-...” 这种循环来做调试,真是会疯了的。所以我能静下心好好研究研究如何写Golang的单元测试了。

数据库怎么办?

这个是第一个需要思考的问题。这个问题和语言无关。一旦有数据库操作,就需要考虑如何在测试用例中如何处理数据库操作。我想了想,无外乎两种做法,一种是直接mock数据库的返回对象。另外一种,是搭建一个测试DB,然后灌入假数据,进行测试。这两种方式我选择了后一种。有几个理由:首先,mock数据库返回数据是一个比灌入DB数据更为复杂的逻辑,数据库返回的数据根据sql各种各样,要想在每个环节都写好数据库操作返回,倒不如我直接伪造一些数据来的方便。其次,mock数据库返回会丢失model层的测试逻辑,当然如果你是轻model层,整个model就只有一个orm,这个可能就不是理由了。

所以,我操作的第一步,从线上把数据库表结构copy一份到我本地vagrant的mysql中。

这里必须要注意,你的测试数据库和测试代码最好是同一个机器上,否则每跑一个测试用例,消耗的时间非常大,你的测试体验也不会太好。

第三方请求怎么办?

我的代码逻辑中也有一些第三方调用,调用其他服务。当然这里也有同样的两种办法,一种是直接在本地测试环境搭建第三方服务,另外一种是mock第三方服务的返回数据。这里我选择了mock数据的方式。基本想法是因为我这个测试毕竟不是一种全链路测试,测试的主体还是我的服务,我的服务基本上只包含服务+DB,如果要搭建第三方服务,这就有点舍本逐末的感觉了。

好了,如何mock第三方服务呢?

查了下golang中mock的包有两个比较出名,一个是golang官网出品的golang/mock,另外一个是monkey(https://github.com/bouk/monkey)。两个相比之下,我感觉golang/mock是师出有名,但是不如monkey好用,monkey属于黑科技,使用修改函数指针的方式进行mock函数。我想了想,实用第一位,投入了monkey的怀抱。

基本使用代码如下:

// mock路网接口

guard := monkey.Patch(lib.Curl, func(trace *lib.TraceContext, CurlType, urlString string, data url.Values, addToken bool) ([]byte, error) {

return []byte("{[\"10010\":\"后厂村路\"}"]), nil

})

defer guard.Unpatch()

将lib.Curl整个函数给mock了,并且在函数结束后修改mock的函数,保证不影响其他测试用例。

配置文件怎么办?

web服务一般都会有读取配置的代码,我的服务是读取一个参数config=base.json来进行配置的读取的。go test中是没有办法给test的代码传递参数的,(我看网上的一些文章说有个-args的参数,但是我在go1.11版本中确实没有看到这个参数)。于是我只能选择使用环境变量的方式。在运行go test的时候,在最开头的部分设置下当前这个go test的环境变量CONFIG_PATH,然后修改下我的初始化配置文件的代码,允许传入参数进行配置文件的读取。

大概代码如下:

在运行go test的时候设置环境变量:

CONFIG_PATH=/home/vagrant/foo/conf/yejianfeng/base.json go test foo/signaledit/... -v -test.run TestGetGroups

测试环境的初始化配置文件逻辑:

package test

import (

..

)

var HasSetup = false

// signalEdit初始化,只调用一次

func SetUpSignalEdit() {

if HasSetup == false {

gin.SetMode(gin.TestMode)

confPath := os.Getenv("CONFIG_PATH") // 获取环境变量

commonlib.Init(confPath, "") // 初始化配置文件

conf.ParseLocalConfig()

db.InitDB()

HasSetup = true

}

DestroyTestData(db.EditDB)

CreateTestData(db.EditDB)

}

web怎么进行单元测试?

关于这个,httptest这个包提供给我们想要的逻辑了,网上的文章也一大堆了。使用起来也是很方便,

router := gin.New()

jc := Controller{}

// 灯组模型表获取信息

router.GET("/group/all", jc.GroupAll)

...

//构建返回值

w := httptest.NewRecorder()

//构建请求

r, _ := http.NewRequest("GET", "/group/all?logic_junction_id=test_junction", nil)

//调用请求接口

router.ServeHTTP(w, r)

resp := w.Result()

body, _ := ioutil.ReadAll(resp.Body)

就没有什么好说的了。

关于数据初始化和销毁

既然我选择使用本地DB进行测试,那么按照逻辑,需要在测试用例开始初始化DB数据,然后在测试用例结束后销毁数据。这里我还选择在测试用例开始的时候,先销毁数据,然后初始化数据,测试用例结束的时候不要销毁数据。这样做我承认有不好的地方,就是有可能会有脏数据。比较好的地方,就是我在单个测试用例跑完的时候,我有机会去数据库看一眼现在数据库里面的测试数据是什么样子。

不管怎么洋,数据初始化和销毁的工作就变得异常重要了,它们必须是幂等,而且可以循环幂等。(销毁-初始化)=(销毁-销毁-初始化)=(初始化-销毁-初始化)。要做到这个我的感受必须借助具体的业务数据表逻辑了。比如我的所有数据表都有一个路口id的字段,那么我就很容易做到销毁的幂等,我每次销毁的时候,就只要把这个路口的所有数据删除就可以了。如果没有的话,由于我们的数据库是本地数据库,不妨采用整个数据表清空的方式操作。

数据初始化和销毁的函数我封装成两个函数,放在一个包里面

var (

SignalID = int64(999999)

LogicJunctionId = "test_junction"

)

// 创建测试数据

func CreateTestData(db *gorm.DB) {

// SignalInfo表创建一条数据

signalInfo := &models.SignalInfo{}

signalInfo.Id = SignalID

signalInfo.Name = "测试路口id"

signalInfo.LogicJunctionId = LogicJunctionId

signalInfo.Status = 1

db.Create(signalInfo)

}

// 销毁测试数据

func DestroyTestData(db *gorm.DB) {

db.Delete(&models.SignalInfo{}, "logic_junctionid=" + LogicJunctionId)

...

}

然后把上面说的初始化操作封装成一个函数

var HasSetup = false

// signalEdit初始化,只调用一次

func SetUpSignalEdit() {

if HasSetup == false {

gin.SetMode(gin.TestMode)

confPath := os.Getenv("CONFIG_PATH")

commonlib.Init(confPath, "")

conf.ParseLocalConfig()

db.InitDB()

HasSetup = true

}

DestroyTestData(db.EditDB)

CreateTestData(db.EditDB)

}

所有测试用例都先调用下这个函数

func TestGetGroups(t *testing.T) {

test.SetUpSignalEdit()

...

}

这里真心要吐槽下testing框架,既然做了测试框架,SetUp函数,SetDown函数这些都不考虑,和主流的测试框架的思想真的有点偏差,导致像这种“普通”的初始化的需求都要自己写方法来绕过,至少testing框架为应用思考的东西还是太少了。

测试用例的粒度

我一直知道写好测试用例是一个难度不亚于开发的工作。测试用例有粒度问题,我觉得,测试用例的粒度宜大不宜小。我这个项目是controller-service-mmodels架构,controller一个函数就是一个接口,service一个函数是一个通用性比较高的服务,model是比较瘦的model,基本只做增删改查。在我这个架构中,我写的测试用例粒度大多数是controller级别的,有少数是service级别的,model级别的测试用例基本没有。

测试用例粒度大一些,有个明显的好处,就是对需求的容忍度高了很多。一般测试用例最痛的就是需求一旦修改了,我的业务逻辑就修改了,我的测试用例也要跟着修改。修改测试用例是很痛苦的事情。所以如果测试用例足够大,比如和接口一样大,那么基本上,由于业务接口的兼容性要求,我们的测试用例的输入输出一般不会进行大的变动(虽然里面的service或者model会进行比较大的变动)。这样有一些需求变化了之后,我甚至不需要修改任何测试用例的代码就可以。

当然有的测试用例粒度太大,一些小的分支可能就测试不到,或者很难构建测试数据,所以有的时候,还是需要一写稍微小一点的粒度的测试用例。

另外对于不需要依赖测试数据的类库函数,如果你对这个类库函数的输入输出的需求变更有把握控制的话,(你需要对自己的这个判断负责)这种类库函数的测试用例则是越细越好。

其他原则性的东西

说说写测试用例的一些原则性的东西。

检验逻辑抗需求变更能力越强越好

首先,测试用例的检验逻辑不是越全越好,而且有很多技巧。比如一个插入的接口,你测试是否插入成功,有很多时候,你根据判断插入条数是否多一条会比你判断这个插入条数的所有字段是否是你要求的更好。原则还是那个,测试用例的抗需求变更能力会更高,首先基本上如果我的插入逻辑很简单,那么插入成功就约等于插入的每个字段都满足,当然这里是约等于,但是因为业务代码也是我自己写的,心里这个B数还是有的。然后,如果一旦需求变更我这个数据多了一个字段,那么我这个测试用例基本不需要做任何修改就还可以继续跑起来。

再次强调下,这里的约等于的判断就是看你对你业务代码的感觉了。

并不是所有的错误都需要完美处理

测试代码毕竟不像业务代码那么需要完美的严谨,所有的panic都是欢迎的。换句话说,我们业务代码基本上对所有error都需要有所处理,但是测试用例并不一定了。如果我在上一行代码中没有处理这个error,那么我传递给下一行的参数很可能就是nil,很有可能在下一行代码中直接panic了一个错误出来。这个也能让我发现我的错误。

所以,测试用例并不需要写那么严谨,有的地方直接panic错误也是一个很好的选择。

Fatal和Error的选择

基本上我觉得Error没啥用,我目前的测试用例都要求所有的判断节点都跑成功,任何一个地方失败了,直接就报错进行调试。我的精力也不允许我一次性能处理多个错误case,基本上调试失败的测试用例是一个个调试的,所以error并没有什么用。

这点纯粹我个人观点,估计会有很多人不同意。

检验逻辑多用变量

检验逻辑尽量少用 response.Name == "测试路口" 这种代码,能尽量找到替换"测试路口" 这个的变量尽量使用变量,同样的理由,测试用例的抗需求变更能力会更高。

总结

测试用例是开发人员最后一块遮羞布,写Golang的代码和写PHP的代码确实体验完全不一样,在Golang代码中,首先写测试用例异常方便了。其次,Golang的调试成本远远高于PHP,写测试用例看起来是浪费时间,实际上是节省你的调试时间。最后,golang代码的每次重构(增加一个字段,少一个字段)影响的文件数远远高于PHP,如果没有这块遮羞布,你怎么确保你的代码修改后还能正常运行呢?

Just Testing!

▌上图

b1081bfc412d7b8d089741d31919887e.png

▌你可能对这些文章感兴趣

 vimium使用

② 聊聊osm

③ golang的cms

④ 一次composer错误使用引发的思考

⑤ colly源码学习

⑥ 使用chan的时候选择对象还是指针

⑦ golang中Context的使用场景

⑧ 论凛冬

⑨ 如何加速golang业务的开发速度

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/409816.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android 进度条_Android仿水波纹流球进度条控制器,实现高端大气的主流特效

今天看到一个效果挺不错的,就模仿了下来,加上了一些自己想要的效果,感觉还不错的样子,所以就分享出来了,话不多说,上图CircleView这里主要是实现中心圆以及水波特效package com.lgl.circleview;import andr…

docker php composer 使用_如何使用Docker部署PHP开发环境

本文主要介绍了如何使用Docker构建PHP的开发环境,文中作者也探讨了构建基于Docker的开发环境应该使用单容器还是多容器,各有什么利弊。推荐PHP开发者阅读。希望对大家有所帮助。环境部署一直是一个很大的问题,无论是开发环境还是生产环境&…

from 下拉框多个值提交_Git提交规范

规范的作用大多数情况下,看提交历史的人跟提交代码的人都不是同一个人,当别人阅读你的提交历史时,他很可能是不知道具体代码细节的,你如何在最短的时间内让他一眼知道每次提交的意义:每次提交影响的具体范围&#xff1…

【物联网智能网关-03】GPRS模块中文短信收发

在去年年初,就已经推出V1.0.0的GPRS库,不过在这个版本上只是实现了西文短信收发和字符串方式的GPRS数据通信,功能还相对不完善(参见我以前的博文《GPRS通信实现》)。最近升级的版本,对以上功能进行了大幅度…

蓄电池单格电压多少伏_蓄电池充电规范手册

很多用户在买完蓄电池之后第一想法就是赶紧充电,很多陋习让用户使用行为造成了新买的蓄电池没怎么用感觉就和旧的没啥区别。而且使用时间越来越短到头来企业还失去了很多的客户,德国阳光蓄电池手册整理整编了在不同的环境中我们该如何很好的去维护自己的…

钉钉机器人关键词应答_除了用于电销,智能语音机器人可以应用哪些地方?

之前的文章探讨的是智能语音机器人在电销行业的应用,然而在实际的场景中,电销行业的应用只是大家所熟知的行业之一。对比于人工电销,使用智能语音机器人有着诸多优势,例如:工作效率高、意向筛选、电话录音并转化为文字…

1-2docker-基本的使用

1、Docker 官⽅提供了⼀个公共的镜像仓库 https://hub.docker.com2、获取镜像 docker pull [选项] [Docker Registry 地址[:端⼝]/]仓库名[:标签]3、运行镜像 docker run -it --rm ubuntu:16.04 /bin/bash -it:这是两个参数,⼀个是 -i:交互式…

assert函数_PHP 之 assert()函数

assert()函数其实是一个断言函数。那么什么是断言呢?百度百科上是这么说的:编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。说到这里,大家应该能知道assert()函数是干嘛用的了吧?好…

1-3docker commit定制镜像

以定制⼀个 Web 服务器为例⼦1、commit定制镜像 docker pull nginx:1.17运行容器 --name:容器名字 -d:后台 -p本地端口:容器内端口 docker run --name webserver -d -p 8080:80 nginx:1.17#进入容器 docker exec -it webserver /bin/bash#进入容器执…

mysql 启动_mysql安装、启动

在这个网址下载的:Download MySQL Community Server​dev.mysql.com下载后解压到了D盘,我是重命名为mysql。进去目录下bin子目录,进行以下操作,初始化:mysqld --initialize --console执行完成后,会输出 roo…

2010 Stanford Local ACM Programming Contest-H解题报告

题意是说,给出一些道路,要修建一条高速公路,高速公路不能分叉,而且是在给出的图中的一些路径组成,求的是不在高速公路上的点离高速公路的最远距离的最小值是多少。首先要找到一条这个图中的关键路径,既最长…

qtmessagebox对话框里自定义按钮文本_按钮你可以这样设计

作者:Michal Malewicz译者:Matrix审稿:afang原文链接:https://uxdesign.cc/design-better-buttons-a5c90a113280文章由交译所成员翻译,如需转载,请先申请授权。译文如下:按钮是触发它所描述功能…

zabbix入门之添加主机

添加主机的方法有两种:手动添加、自动发现 前提是:在被监控主机中安装zabbix-agent、zabbix-sender组件,并配置好启动服务。 手动添加: 自动发现: 这里等待1分钟左右即可发现主机 开启默认的动作 等待几分钟后即可在“…

如何保持连接_工高连城 | 连接器连接失效的原因有哪些

【温馨提示】本公众号是工高电子旗下工高连城中国连城双电商平台的官方公众号,简称工高连城连接器商城 中国连城平台定位:中国连接器行业专业供应链服务平台中国连接器行业的阿里巴巴永不落幕的online线上慕尼黑连接器展会。中国连城官网:w…

centos7删除文件命令_干货 | 玩转云文件存储——利用CFS实现web应用的共享访问...

京东云文件服务(Cloud File Service,以下简称:CFS)是一种高可靠、可扩展、可共享访问的全托管分布式文件系统。它可在不中断应用服务的情况下,根据您对文件系统的使用,按需扩展或缩减,并按照实际用量计费。采用NFS协议&#xff0c…

1-4dockerfile基本使用

1.创建一个文件夹 mkdir mynginxcd mynginxtouch Dockerfile [rootVM_0_10_centos mynginx]# cat Dockerfile FROM nginx:1.17 #第一次镜像RUN echo echo <h1>Hello, zjy!</h1> > /usr/share/nginx/html/index.html1-1、如果说没有第一层镜像&#xff0c;是…

zTree v2.6 - v3.0 文件对比

转载于:https://www.cnblogs.com/MyFlora/archive/2012/06/05/2536377.html

lvs服务器需要开启web服务么_Centos7搭建LVS+Keepalived高可用Web

LVS Keepalived 高可用集群Keepalived的设计目标是构建高可用的LVS负载均衡的集群&#xff0c;可以调用ipvsadm工具创建虚拟机&#xff0c;不仅仅用作双机热备&#xff0c;还可以使用keepalived构建更加方便快捷的节点&#xff0c;进行相关的健康检查&#xff0c;自动移除失效…

1-5docker私有镜像仓库

1、简单操作 1、在 https://cloud.docker.com 免费注册一个 Docker 账号 2、登录 docker login #命令登录 Docker Hub。 3、注销docker logout # 退出登录。 拉取镜像 4、docker search #命令来查找官方仓库中的镜像 5、docker pull 命令来将它下载到本地。#推送镜像到自己…

NCoreCoder.Aop详解

于今天&#xff0c;功能终于完善度到比较满意的程度了 准备好好写一篇文章&#xff0c;而不是之前的流水账&#xff0c;分享一下最近这些天的踩坑 一开始AOP选的微软提供的DispatchProxy 关于这个&#xff0c;有大佬的文章&#xff0c;可以看看&#xff0c;了解一下 https://ww…