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了捏!
好的,以上就是对社区话题页面需求的整个实现流程,这样我们从项目拆解,代码设计落地,最后测试运行就跑通了整个的项目流程,为大家后期实现项目提供了一定的开发思路。当然实际项目较我们实现的需求会复杂很多,不过大家也不必担心,可以通过大拆小的思路,将大需求拆解为小需求的思路来分析解决,遇到问题,各个击破,同时做好充分的测试。 课后作业
非常感谢您阅读到这里,如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 收藏 💕评论💬感谢支持!!!
相关文章:

Go语言工程实践之测试与Gin项目实践
Go 语言并发编程 及 进阶与依赖管理_软工菜鸡的博客-CSDN博客 03 测试 回归测试一般是QA(质量保证)同学手动通过终端回归一些固定的主流程场景 集成测试是对系统功能维度做测试验证,通过服务暴露的某个接口,进行自动化测试 而单元测试开发阶段,开发者对单独的函数…...

排查docker无法启动问题
查看Linux系统操作日志(最后200行就可以排查): tail -200f /var/log/messages...

[C++ 网络协议] 套接字和地址族、数据序列
目录 1. 套接字 1.1 在Linux平台下构建套接字 1.1.1 用于接听的套接字(服务器端套接字) 1.1.2 用于发送请求的套接字(客户端套接字) 1.2 在Windows平台下构建套接字 1.2.1 Winsock的初始化 1.2.2 用于接听的套接字(服务器端套接字) 1.2.3 用于发送请求的套接字(客户端套…...

AI 绘画Stable Diffusion 研究(八)sd采样方法详解
大家好,我是风雨无阻。 本文适合人群: 希望了解stable Diffusion WebUI中提供的Sampler究竟有什么不同,想知道如何选用合适采样器以进一步提高出图质量的朋友。 想要进一步了解AI绘图基本原理的朋友。 对stable diffusion AI绘图感兴趣的朋…...
线程池满了如何处理
某天搬砖时遇到一个问题,我创建了一个线程池执行任务,刚开始的时候还是一切,结果第二天发现有些任务没有正常执行。一看日志才发现是高峰期时线程池给我占用慢了,任务被丢掉了。 举个例子,我创建了一个线程池&#…...

Java多线程编程中的线程间通信
Java多线程编程中的线程间通信 基本概念: 线程间通信是多线程编程中的一个重要概念,指的是不同线程之间如何协调和交换信息,以达到共同完成任务的目的。 线程间通信的目的 是确保多个线程能够按照一定的顺序和规则进行协作ÿ…...
write javaBean error, fastjson version 1.2.76
fastjson JSON.toJSONString 报错: > [0] JavaBeanSerializer.java->541: com.alibaba.fastjson.serializer.JavaBeanSerializer->write()> [1] JavaBeanSerializer.java->154: com.alibaba.fastjson.serializer.JavaBeanSerializer->write()>…...

Tomcat的部署及优化(多实例和动静分离)
目录 绪论 1、tomact 1.1 核心组件 1.2 什么是 servlet 1.3 什么是 JSP? 1.4 Tomcat 功能组件结构 1.5 Tomcat 请求过程 2、Tomcat 服务部署 2.1 tomcat自身优化: 2.2 内核优化 2.3 jvm 2.3.1 jvm配置 2.3.2 Tomcat配置JVM参数 2.3.3 jvm优化 3、tom…...

品牌推广革新之道:海外网红与内容营销的融合
随着数字时代的来临,品牌推广的方式正在经历着革命性的变化。传统的广告手段逐渐失去了吸引力,而内容营销正成为品牌推广的新宠儿。尤其是海外网红的崛起,不仅改变了推广方式,更重新定义了品牌与消费者之间的互动关系。本文Nox聚星…...

【 BERTopic应用 02/3】 分析卡塔尔世界杯推特数据
摄影:Fauzan Saari on Unsplash 一、说明 这是我们对世界杯推特数据分析的第3部分,我们放弃了。我们将对我们的数据进行情绪分析,以了解人们对卡塔尔世界杯的感受。我将在这里介绍的一个功能强大的工具包是Hugging Face,您可以在…...
TypeScript教程(三)变量声明
一、变量声明 变量是一种使用方便的占位符,用于引用计算机内存地址,可以将变量看做存储数据的容器 命名规则: 1.变量名称可以包含数字和字母 2.除了下划线_和美元$符号外,不能包含其他特殊字符,包括空格 3.变量名…...

【数据结构】堆的实现,堆排序以及TOP-K问题
目录 1.堆的概念及结构 2.堆的实现 2.1初始化堆 2.2销毁堆 2.3取堆顶元素 2.4返回堆的大小 2.5判断是否为空 2.6打印堆 2.7插入元素 2.8堆的向上调整 2.9弹出元素 2.10堆的向下调整 3. 建堆时间复杂度 4. 堆的应用 4.1 堆排序 4.2 TOP-K问题 1.堆的概念及结构 …...

释放马氏距离的力量:用 Python 探索多元数据分析
一、说明 马哈拉诺比斯距离(Mahalanobis Distance)是一种测量两个概率分布之间距离的方法。它是基于样本协方差矩阵的函数,用于评估两个向量之间的相似程度。Mahalanobis Distance考虑了数据集中各个特征之间的协方差,因此比欧氏距…...

【不限于联想Y9000P电脑关盖再打开时黑屏的解决办法】
不限于联想Y9000P电脑关盖再打开时黑屏的解决办法 问题的前言问题的出现问题拟解决 问题的前言 事情发生在昨天,更新了Win11系统后: 最惹人注目的三处地方就是: 1.可以查看时间的秒数了; 2.右键展示的内容变窄了; 3.按…...

策略模式实战应用
场景 假设做了个卖课网站,会员等级分为月vip、年vip、终生vip,每个等级买课的优惠力度不一样,传统的写法肯定是一堆的 if-else,现在使用策略模式写出代码实现 代码实现 策略模式的核心思想就是对扩展开放,对修改关闭…...
JAVA集合-Map
// 【Map】:双列集合,键值对形式存储,映射关系(kay,value) // 实现:HashMap // 子接口:SortedMap Map的子接口 // 实现类:TreeMap // HashMap // 1。可以插入null // …...

利用Simulink Test进行模型单元测试 - 1
1.搭建用于测试的简单模型 随手搭建了一个demo模型MilTestModel,模型中不带参数 2.创建测试框架 1.模型空白处右击 测试框架 > 为‘MilTestModel’创建 菜单 2.在创建测试框架对话框中,点击OK,对应的测试框架MilTestMode_Harness1就自动…...
深入探讨代理技术:保障网络安全与高效爬虫
1. Socks5代理与IP代理的区别与应用 Socks5代理和IP代理是代理技术中的两个重要方面,它们有着不同的特点和应用场景。Socks5代理是一种协议,支持TCP和UDP流量传输,适用于需要实时数据传输的场景,例如在线游戏或实时通信应用。而I…...

HDMI接口的PCB布局布线要求
高清多媒体接口(High Definition Multimedia Interface),简称:HDMI,是一种全数字化视频和声音发送接口,可以发送未压缩的音频及视频信号。随着技术的不断提升,HDMI的传输速率也不断的提升&#…...
Linux tar包安装 Prometheus 和 Grafana(知识点:systemd Unit/重定向)
0. 介绍 用tar包的方式安装 Prometheus 和 Grafana Prometheus:开源的监控方案Grafana:将Prometheus的数据可视化平台 Prometheus已经有了查询功能为什么还需要grafana呢?Prometheus基于promQL这一SQL方言,有一定门槛!Grafana基于浏览器的操作与可视化图表大大降低了理解难…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...