【Go语言进阶】单元测试-基准测试-项目实战 | 青训营笔记

98 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天,今天学习的内容是Go的单元测试、基准测试以及项目实战,整理学习笔记如下。

3.3 Go语言进阶 - 单元测试

从单元测试实践出发,提升质量意识

代码:go-project-example/test

3.3.1 测试三种类型

image.png

从上到下,覆盖率增加,成本下降。

回归测试: 使用人力对相关功能及代码逻辑进行测试。

集成测试: 通过一些自动化方法进行的回归测试。

单元测试: 开发阶段,开发者对相应函数模块等进行测试。

3.3.2 单元测试概述

image.png

规范规则:

  • 文件名: 所有测试文件以(测试对应文件文件名+) _test.go 结尾

  • 函数名: 测试函数以 func TestXxx(t *testing.T) 命名(格式正确的测试函数在IDE中有相应的功能) image.png

  • 初始化逻辑放到 TestMainimage.png

实例:

TestHelloTom,想要输出为“Tom”,使用 asset 包与 testing 包,点击绿色小三角进行测试

image.png

测试通过,符合要求

覆盖率

单元测试的评价标准 - 代码覆盖率

示例:

image.png

以上仅测试了返回值为 true 的情况,没有测试返回值为 false 的情况,代码覆盖率为 66.7 (使用 --cover 参数)

image-20230116183547276.png

增加测试返回值为 false 的情况,代码覆盖率为 100%

Tips:

  • 一般覆盖率: 50%~60% ,较高达到 80%+
  • 测试分支相对独立、全面覆盖
  • 测试单元粒度足够小,函数单一职责

3.3.3 单元测试 - 依赖

image.png

外部依赖 => 幂等&稳定

幂等: 单元测试结果固定;稳定: 单元测试相互隔离,独立运行。

3.3.4 单元测试 - Mock

解决测试需要外部依赖的问题,保证幂等与稳定性

monkey:github.com/bouk/monkey

快速Mock函数

  • 为一个函数打桩
  • 为一个方法打桩

Patch: 将内存中函数地址进行替换,使最终执行时调用运行打桩函数 Unpatch: 卸载恢复打桩函数

image.png

实例:

存在文件依赖,无法保证测试函数的幂等与稳定

image.png

用Mock进行替换,没有强依赖,保证测试函数的幂等与稳定性

image.png

3.4 Go语言进阶 - 基准测试

测试一段程序的运行性能与CPU运行损耗等

代码:go-project-example/benchmark

规范规则:

  • 文件名: 所有测试文件以(测试对应文件文件名+) _test.go 结尾

  • 函数名: 测试函数以 func BenchmarkXxx(b *testing.B) 命名(格式正确的测试函数在IDE中有相应的功能)

  • 初始化时间:

    image.png

示例:

随机返回一个服务器的Index

image.png

并行版本(由于临界区并发限制,性能有部分损耗)

image.png

使用快速rand版本(性能显著提高)

image.png image.png

3.5 Go语言项目实战

通过项目需求、需求拆解、逻辑设计、代码实现感受真实的项目开发

3.5.1 需求背景

需求模型来源

青训营话题页

image-20230116204550298.png

需求

  1. 实现一个展示话题(标题,文字描述)和回帖列表的后端http接口;
  2. 话题和回帖数据用本地文件存储
  3. 暂不考虑前端页面的实现,仅仅实现一个本地web服务

3.5.2 需求分析与项目规划

页面需要的实体为 - 话题与帖子

ER图

image.png

分层结构

  • 数据层:数据Model,外部数据的增删改查
  • 逻辑层:业务Entity,处理核心业务逻辑输出
  • 视图层:视图View,处理和外部交互的逻辑

image.png

组件及技术点

3.5.3 实现步骤

初始化go mod

创建文件夹,执行命令

go mod init my-goproject

创建 go.mod 文件

image.png

获取gin框架

下载gin框架

go get -u github.com/gin-gonic/gin
#go get -u gopkg.in/gin-gonic/gin.v1@v1.3.0

Tips:

如果使用第二个命令,会出现以下报错,原因是在使用 GOPROXY 的时候,开启了 GO111MODULE ,导致包管理非官方所说的在 $GOPATH\src\,而是去了 $GOPATH\pkg\ 目录下

image.png

直接使用 edit 命令编辑,再下载即可

go mod edit -require gopkg.in/gin-gonic/gin.v1@v1.3.0
go mod download

可以看到生成了 go.sum 文件,包导入成功

image.png

Repository 数据层实现

我们从示例代码中拷贝 /data 数据文件夹,其中包含 topicpost 两个文件,存放话题与帖子的信息

image.png

要实现的是对该话题与帖子信息的查询

image.png

拷贝 /repository 代码文件夹,其中包含三个文件,数据库初始化以及话题与帖子的查询。

db_init.go 文件中,我们使用 map 将数据通过索引进行映射

image.png

初始化话题数据索引: 打开对应的数据文件,通过迭代器的方式对数据行进行遍历,用 json.Unmarshal 将数据从json格式转换存储到结构体中

image.png

根据话题数据索引进行查询:topic.go 文件中,我们根据话题ID作为索引进行话题数据查询

image.png

Tips: sync.Once

参考文档:Go sync.Once

其中使用的 sync.Once 是Go标准库提供的使函数只执行一次的实现,常应用于高并发下只执行一次的场景,如单例模式。通常使用进行初始化配置、保持数据库连接等。其可以减少存储浪费,提高性能。

在多数情况下,sync.Once 被用于控制变量的初始化,这个变量的读写满足如下三个条件:

  • 当且仅当第一次访问某个变量时,进行初始化(写);
  • 变量初始化过程中,所有读都被阻塞,直到初始化完成;
  • 变量仅初始化一次,初始化完成后驻留在内存里。

sync.Once 仅提供了一个方法 Do,参数 f 是对象初始化函数。

func (o *Once) Do(f func())

原理: 保证变量仅被初始化一次,需要有个标志来判断变量是否已初始化过,若没有则需要初始化;同时要保证线程安全,支持并发,需要互斥锁来实现。

然后,我们参考以上实现类似的帖子信息的索引与查询。

Service 逻辑层实现

拷贝 /server 代码文件夹,其中包含两个文件

image.png

代码的实现按照以上三个流程

image.png

准备数据部分,由于两部分信息不存在依赖,因此使用并行执行

image.png

Tips:

其中要用到 assert 包中的等于不等于等方法

image.png

我们需要下载并安装该包到依赖

go get -u github.com/stretchr/testify/assert

补充get参数:

附加参数备 注
-v显示操作流程的日志及信息,方便检查错误
-u下载丢失的包,但不会更新已经存在的包
-d只下载,不安装
-insecure允许使用不安全的 HTTP 方式进行下载操作

image.png

下载完成,查看 go.mod ,已经包含该依赖

image.png

Controller 视图层实现

拷贝 /controller 代码文件夹,其中包含一个文件

在其中我们构建View对象以及定义业务错误码

Router

通过Gin搭建Web框架,拷贝代码文件 server.go ,其为一个函数入口,包名为 main ,放在根目录下。

  1. 初始化数据索引
  2. 初始化引擎配置
  3. 构建路由
  4. 启动服务

Tips: 注意在以上拷贝的代码文件中需要将项目文件的包名改成我们自己定义的包名。

image.png

3.5.4 运行

运行gin框架

go run server.go

开一个新终端,用 curl 命令进行访问,比如我们访问第二页

curl http://127.0.0.1:8080/community/page/get/2

image.png

可以看到此时成功查询并返回了结果。这样,一个基于Gin的简单后端框架就搭建完成了。