Go语言工程实践之测试与Gin项目实践

Go 语言并发编程 及 进阶与依赖管理_软工菜鸡的博客-CSDN博客

03 测试

回归测试一般是QA(质量保证)同学手动通过终端回归一些固定的主流程场景

集成测试是对系统功能维度做测试验证,通过服务暴露的某个接口,进行自动化测试

单元测试开发阶段,开发者对单独的函数、模块做功能验证

层级从上至下,测试成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率一定程度上决定着代码的质量

3.1.0 单元测试

单元测试主要包括:输入,测试单元,输出,以及校对

单元的概念比较广:包括接口,函数,模块等;用最后的校对来保证代码的功能与我们的预期相符;

单侧一方面可以保证质量,在整体覆盖率足够的情况下,一定程度上既保证了新功能本身的正确性,又未破坏原有代码的正确性。 另一方面可以提升效率,在代码有bug的情况下,通过编写单测,可以在一个较短周期内定位和修复问题

3.1.1 单元测试-规则

基本规范,以 _test.go结尾这样从文件上就很好了区分源码和测试代码

以Test开头,且连接的第一个字母大写

TestMain的m.run()会跑这个package下的所有单测

3.1.2 单元测试-例子

HelloTom() 由于代码逻辑的错误没有输出Tom

接下来进行测试:TestHelloTom()

3.1.3 单元测试-运行

输出结果错误

3.1.4 单元测试-assert

import 了testify/assert 的Equal函数

3.1.5 单元测试-覆盖率

这是一个判断是否及格的函数,超过60分,返回true,否则返回false

右边是对输入为70 的单元测试,我们执行右边的单侧

通过指定go test [] --cover参数,我们看输出了 覆盖率为66.7。

一共三行,我们的单测试执行了2行,所以为66.7

下一步就是提升覆盖率,我们可以增加一个不及格的测试case,重新执行所有单侧,最终覆盖率为100%。

也就是说,我们通过不断对各个分支的测试,保证了覆盖率和完备性。

3.1.6 单元测试-Tips

实际项目中,一般的要求是50%~60% 覆盖率,而对于资金型服务,覆盖率可能要求达到80%;

我们做单元测试,测试分支相互独立,全面覆盖,则要求函数体足够小,这样就比较简单的提升覆盖率,也符合函数设计的单一职责。

3.2 单元测试-依赖

工程中复杂的项目,一般会依赖数据库、缓存等,而我们的单测需要保证稳定性和幂等性

稳定是指相互隔离,能在任何时间,任何环境,运行测试

幂等是指每一次测试运行都应该产生与之前一样的结果。而要实现这一目的就要用到mock机制

3.3 单元测试-文件处理

下面举个例子,打开文件读入 将文件中的第一行字符串中的11替换成00,执行单测,测试通过,而我们的单测需要依赖本地的文件,如果文件被修改或者删除测试就会fail

为了保证测试case的稳定性,我们对读取文件函数进行mock,屏蔽对于文件的依赖

3.4 单元测试-Mock

monkey是一个开源的mock测试库,可以对method,或者实例方法的方法进行mock(打桩)

打桩指的是:用一个函数A 替换函数B,B作为原函数;A作为打桩函数

Mock Patch(原函数,需要打桩的函数) 的作用域在 Runtime;

Unpatch(打桩函数):打桩结束后把原函数卸载掉

monkey主要是在运行时通过 Go 的 unsafe 包,能够将内存中函数的地址替换为运行时函数的地址,在测试中调用的就是运行时的打桩函数B地址。

下面是一个mock的使用样例,通过patch对Readfineline进行打桩mock,默认返回line110,这里通defer卸载mock,这样整个测试函数就摆脱了本地文件的束缚和依赖

3.5 基准测试

Go 语言还提供了基准测试框架,基准测试是指测试一段程序的运行性能及耗费 CPU 的程度

而我们在实际项目开发中,经常会遇到代码性能瓶颈,为了定位问题经常要对代码做性能分析,这就用到了基准测试。使用方法类似于单元测试,

3.5.1 基准测试-例子

这里举一个服务器负载均衡的例子,首先我们有10个服务器列表,每次随机(rand包)执行select函数随机选择一个执行。

3.5.2 基准测试-运行

基准测试以Benchmark开头,入参是testing.B, 用b中的N值反复递增循环测试(对一个测试用例的默认测试时间是 1 秒,当测试用例函数返回时还不到 1 秒,那么 testing.B 中的 N 值将按 1、2、5、10、20、50……递增,并以递增后的值重新进行用例函数测试

Resttimer重置计时器,我们在reset之前做了init或其他的准备操作,这些操作不应该作为基准测试的范围

runparallel()函数是多协程并发测试;执行 2个基准测试,发现代码在并发情况下存在劣化,主要原因是rand为了保证全局的随机性和并发安全,持有了一把全局锁

3.5.3 基准测试-优化

GitHub - bytedance/gopkg: Universal Utilities for Go

而为了解决这一随机函数性能问题,开源了一个高性能随机数方法fastrand,上面有开源地址;我们这边再做一下基准测试,性能提升百倍。主要的思路是牺牲了一定的数列的一致性,在大多数场景是适用的,同学在后面遇到随机的场景可以尝试用一下。

4 项目实践

4.0需求背景

大家应该都是从掘金的 社区话题入口报名的,都看到过这个页面,页面的功能包括话题详情,回帖列表,支持回帖,点赞,和回帖回复

我们今天就以此为需求模型,开发一个该页面交涉及的服务端小功能。

4.1 需求描述

                下面是需求的详细描述;Ok,如果该功能由同学自己实现,如何去做模型设计,可以拿笔和纸简单画一下,然后打开ide,争取我们一起完成这个实现,后面跟不上也不要着急,代码已经托管到github,大家可以自行下载查看GitHub - Moonlight-Zhao/go-project-example

4.2 需求用例

我们从用例分析一步步拆解实现,主要涉及的功能点,用户浏览消费,涉及页面的展示,包括话题内容和回帖的列表,其实从图中我们应该会抽出2个实体的,而实体的属性有哪些,他们之间的联系又如何? 大家想一下,可以先定义一下结构体

4.3 ER 图

4.4 分层结构

整体分为三层

repository数据层,service逻辑层,controoler视图层

数据层关联底层数据模,也就是这里的model,封装外部数据的增删改查,我们的数据存储在本地文件,通过文件操作拉取话题,帖子数据

数据层面向逻辑层,对service层透明,数据层屏蔽下游数据差异,也就是不管下游是文件,还是数据库,还是微服务等,对service层的接口模型是不变的。

Servcie逻辑层处理核心业务逻辑接收数据层数据 计算打包业务实体entiy,对应我们的需求,就是话题页面,包括话题和回帖列表,并上送给视图层;

Controller视图层负责处理和外部的交互逻辑,以view视图的形式返回给客户端,对于我们需求,封装json格式化的请求结果,api形式访问就好

4.5 组件工具

下面介绍下开发涉及的基础组件和工具

首先是gin,高性能开源的go web框架,我们基于gin 搭建web服务器,在课程手册应该提到了,这里我们只是简单的使用,主要涉及路由分发,不会涉及其他复杂的概念。

因为我们引入了web框架,所以就涉及go module依赖管理,如前面依赖管理课程内容讲解,我们首先通过go mod是初始化go mod管理配置文件,然后go get下载gin依赖,这里显示用了V1.3.0版本。

有了框架依赖,我们只需要关注业务本身的实现,

从下至上:reposity-->service-->controller,我们一步步实现。希望大家能跟上我的节奏,从0~1 实现这个项目,如果时间问题,大家可以一步步copy一下,主要是走一下开发思路。

4.6 Repository数据层

首先是reposity,我们可以根据之前的er图先定义struct文件中每行的格式如图所示

那如何实现QueryTopicById(话题),QueryPostsByParentId(帖子)

4.6 Repository-index

一方面查询我们可以全扫描遍历的方式,但是这虽然能达到我们的目的,但是并非高效的方式;

所以这里引出索引的概念,索引就像书的目录,可以引导我们快速查找定位我们需要的结果;

这里我们map实现内存索引,在服务对外暴露前,利用文件元数据初始化全局内存索引,这样就可以实现0(1)的时间复杂度查找操作。

下面是具体的实现,首先是os.Open打开文件,基于file 初始化scanner,通过迭代器方式遍历scanner数据行,转化为结构体Topic存储至内存map,这就是初始化话题内存索引

4.6 Repository-查询

有了内存索引,下一步就是实现查询操作就比较简单了

直接根据查询key获得map中的value就好了,这里用到了sync.once,主要适用高并发的场景下只执行一次的场景,这里的基于once的实现模式就是我们平常说的单例模式,减少存储的浪费。

有了topic的查询代码,大家可以照猫画虎 自行实现一下根据话题id查询回帖列表的查询方法

func (*PostDao) QueryPostByParentId(parentId int64) ([]*Post, error) {var posts []*Posterr := db.Where("parent_id = ?", parentId).Find(&posts).Errorif err != nil {util.Logger.Error("find posts by parent_id err:" + err.Error())return nil, err}return posts, nil
}

4.7 Service逻辑层

有了reposity层以后,下面我们开始实现service层,首先我们定义servcie层实体PageInfo,包括:Topic和PostList

下面是具体的servcie流程编排 通过err控制流程退出,正常会返回页面信息,err为nil

func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {if err := f.checkParam(); err != nil {//简单的id校验return nil, err}if err := f.prepareInfo(); err != nil {return nil, err}if err := f.packPageInfo(); err != nil {return nil, err}return f.pageInfo, nil
}

关于prepareInfo方法,话题和回帖信息的获取都依赖topicid,这样2个流程没有相互依赖;

这就可以并行执行,提高执行效率。

WaitGroup Add(2);(2个并行的协程);wait等待两个协程的信息从数据层返回

大家在后期做项目开发中,一定要思考流程是否可以并行,通过压榨CPU,降低接口耗时,不要一味的串行实现,浪费多核cpu的资源

Paramcheck 和pack这里就不讲了,给大家一点时间 编码 ,copy代码

4.8 Controller视图层

这里我们定义一个view对象,通过code msg打包业务状态信息,用data承载业务实体信息,输入

4.9 Router

最后是web服务的引擎配置,path映射到具体的controller。通过path变量传递话题id

4.10 运行

最后执行go run 本地启动web服务,通过curl命令请求服务暴露的接,当然平时写代码不可能像我讲的这么顺畅,难免有bug,大家要做好完备的单元测试,快速定位问题,解决问题。

鼠鼠出bug了捏!

好的,以上就是对社区话题页面需求的整个实现流程,这样我们从项目拆解,代码设计落地,最后测试运行就跑通了整个的项目流程,为大家后期实现项目提供了一定的开发思路。当然实际项目较我们实现的需求会复杂很多,不过大家也不必担心,可以通过大拆小的思路,将大需求拆解为小需求的思路来分析解决,遇到问题,各个击破,同时做好充分的测试。 课后作业

非常感谢您阅读到这里,如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 收藏 💕评论💬感谢支持!!!

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

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

相关文章

day-21 代码随想录算法训练营(19)二叉树part07

530.二叉搜索树的最小绝对差 思路一:二叉搜索树的中序遍历必为升序数组,加入数组后计算相邻两个数差值,即可求出最小绝对差 思路二:同样的思路,中序遍历,直接使用指针记录上一个节点,同时更新…

KAFKA第二课之生产者(面试重点)

生产者学习 1.1 生产者消息发送流程 在消息发送的过程中,涉及到了两个线程——main线程和Sender线程。在main线程中创建了一个双端队列RecordAccumulator。main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到K…

03-基础入门-搭建安全拓展

基础入门-搭建安全拓展 1、涉及的知识点2、常见的问题3、web权限的设置4、演示案例-环境搭建(1)PHPinfo(2)wordpress(3)win7虚拟机上使用iis搭建网站(4)Windows Server 2003配置WEB站…

C#应用处理传入参数 - 开源研究系列文章

今天介绍关于C#的程序传入参数的处理例子。 程序的传入参数应用比较普遍,特别是一个随操作系统启动的程序,需要设置程序启动的时候不显示主窗体,而是在后台运行,于是就有了传入参数问题,比如传入/h或者/min等等。所以此…

YOLO v8目标跟踪详细解读(二)

上一篇,结合代码,我们详细的介绍了YOLOV8目标跟踪的Pipeline。大家应该对跟踪的流程有了大致的了解,下面我们将对跟踪中出现的卡尔曼滤波进行解读。 1.卡尔曼滤波器介绍 卡尔曼滤波(kalman Filtering)是一种利用线性…

欧拉OS 使用 CentOS 7 yum repo

一、下载CentOS的repo的yum文件 任何基于CentOS的yum的repo 的url是这样的: 但欧拉OS输出这个变量为:openEuler 20.03 (LTS-SP3) 那明显欧拉想要使用这个yum的url找不到这个版本, 所以直接讲这个变量替换为 7, Centos 7的7 然后执行&…

wget 详解

wget 详解 wget 详解基本用法:命令参数:递归下载:断点续传:限速下载:后台下载: 示例 wget 详解 wget(Web Get)是一个用于从网络上下载文件的命令行工具,常用于在 Linux …

从零实战SLAM-第七课(多视角几何)

在七月算法报的班,老师讲的蛮好。好记性不如烂笔头,关键内容还是记录一下吧,课程入口,感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…

整型int溢出引起的crash

线上系统发生了crash&#xff0c;后发现是整型溢出。 1、初始化函数的伪代码&#xff1a; init_mem(int count, int size){for(int i0; i<count; i)mem_list[i] i*size; # 溢出发生的地方} 2、问题分析&#xff1a; 原有的变量 i、size 为有符号的int类型&#xff0c;i…

设计模式--策略模式

目录 一.场景 1.1场景 2.2 何时使用 2.3个人理解 二. 业务场景练习 2.1业务: 2.2具体实现 2.3思路 三.总结 3.1策略模式的特点&#xff1a; 3.2策略模式优点 3.3策略模式缺点 一.场景 1.1场景 许多相关的类仅仅是行为有异&#xff0c;也就是说业务代码需要根据场景不…

Android数字价格变化的动画效果的简单实现

原理&#xff1a;使用ValueAnimator属性动画类实现&#xff0c;它通过值的改变手动设置对象的属性值来实现动画效果。直接贴代码&#xff1a; public static void doNumberAnim(TextView tvPrice, float startNumber, float endNumber) {ValueAnimator animator ValueAnimato…

C语言中的 RSA加密和解密算法: 深度探索与实现

C语言中的 RSA加密和解密算法: 深度探索与实现 RSA加密算法是一种非对称加密算法&#xff0c;即公开密钥加密&#xff0c;私有密钥解密。在公开密钥加密和私有密钥解密的过程中&#xff0c;密钥是不同的&#xff0c;这是与其他加密算法的主要区别。RSA算法的安全性依赖于大数分…

ssm+mybatis无法给带有下划线属性赋值问题

原因&#xff1a;mybaitis根据配置&#xff0c;将有下划线的字段名改为了驼峰格式。 具体见&#xff1a;ssmmybatis无法给带有下划线属性赋值问题&#xff0c;无法获取数据库带下划线的字段值 - 开发者博客 解决方式&#xff1a; 直接将实体类中的下划线去掉返回值使用resul…

归并排序 与 计数排序

目录 1.归并排序 1.1 递归实现归并排序&#xff1a; 1.2 非递归实现归并排序 1.3 归并排序的特性总结: 1.4 外部排序 2.计数排序 2.1 操作步骤: 2.2 计数排序的特性总结: 3. 7种常见比较排序比较 1.归并排序 基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种…

代理技术在网络安全、爬虫和数据隐私中的多重应用

1. Socks5代理&#xff1a;灵活的数据中转 Socks5代理协议在网络通信中起着关键作用。与其他代理技术不同&#xff0c;Socks5代理不仅支持TCP连接&#xff0c;还能够处理UDP流量&#xff0c;使其在需要实时数据传输的场景中表现尤为出色。通过将请求和响应中转到代理服务器&am…

redis分布式集群-redis+keepalived+ haproxy

redis分布式集群架构&#xff08;RedisKeepalivedHaproxy&#xff09;至少需要3台服务器、6个节点&#xff0c;一台服务器2个节点。 redis分布式集群架构中的每台服务器都使用六个端口来实现多路复用&#xff0c;最终实现主从热备、负载均衡、秒级切换的目标。 redis分布式集…

使用Edge和chrom扩展工具(GoFullPage)实现整页面截图或生成PDF文件

插件GoFullPage下载&#xff1a;点击免费下载 如果在浏览网页时&#xff0c;有需要整个页面截图或导出PDF文件的需求&#xff0c;这里分享一个Edge浏览器的扩展插件&#xff1a;GoFullPage。 这个工具可以一键实现页面从上到下滚动并截取。 一、打开“管理扩展”&#xff08;…

网络设备(防火墙、路由器、交换机)日志分析监控

外围网络设备&#xff08;如防火墙、路由器、交换机等&#xff09;是关键组件&#xff0c;因为它们控制进出公司网络的流量。因此&#xff0c;监视这些设备的活动有助于 IT 管理员解决操作问题&#xff0c;并保护网络免受攻击者的攻击。通过收集和分析这些设备的日志来监控这些…

Python 3 使用Hadoop 3之MapReduce总结

MapReduce 运行原理 MapReduce简介 MapReduce是一种分布式计算模型&#xff0c;由Google提出&#xff0c;主要用于搜索领域&#xff0c;解决海量数据的计算问题。 MapReduce分成两个部分&#xff1a;Map&#xff08;映射&#xff09;和Reduce&#xff08;归纳&#xff09;。…

tauri-react:快速开发跨平台软件的架子,支持自定义头部和窗口阴影效果

tauri-react 一个使用 taurireacttsantd 开发跨平台软件的模板&#xff0c;支持窗口头部自定义和窗口阴影&#xff0c;不用再自己做适配了&#xff0c;拿来即用&#xff0c;非常 nice。 开原地址&#xff1a;GitHub - Sjj1024/tauri-react: 一个最基础的使用tauri和react开发…