GO语言实践-测试与高质量编程 | 青训营

127 阅读6分钟

测试是避免事故的最后一道屏障。

image.png

1 分类

回归测试 -> 集成测试 -> 单元测试

从左到右覆盖率逐渐变大,成本逐渐降低。

1.1 单元测试

image.png

1.1.1 单元测试-规则

所有测试文件以_test.go 结尾

image.png

func TestXxx(*testing.T)

image.png

初始化逻辑放到TestMain

image.png

1.1.2 单元测试-例子

image.png

image.png

1.1.3 单元测试-运行

go test [flags][packages]

image.png

1.1.4 单元测试-assert

image.png

image.png image.png

1.1.5 单元测试-覆盖率

image.png image.png image.png

image.png image.png

tips:

一般覆盖率:50%~60%,较高覆盖率80%+。

测试分支相互独立,全面覆盖。

测试单元粒度足够小,函数单一职责。

1.2 单元测试-依赖

image.png

外部依赖 => 稳定&幂等

1.3 单元测试-文件处理

image.png image.png

1.4 单元测试-Mock

monkey: github.com/bouk/monkey

快速Mock函数:

1 为一个函数打桩(用一个函数A去替换函数B,B是原函数,A是打桩函数)

2 为一个方法打桩。

1.5 基准测试

优化代码,需要对当前代码分析。

内置的测试框架提供了基准测试的能力。

1.5.1 基准测试-例子

rand 随机选择函数

fastrand 快速选择随机函数

<>> go test -bench=.

2 项目实践

2.1 需求描述

社区话题页面

展示话题(标题,文字描述)和回帖列表

暂不考虑前端页面实现,仅仅实现一个本地web服务

话题和回帖数据用文件存储

2.2 需求用例

浏览消费用户

image.png

2.3 ER图-Entity Relationship Diagram

话题和帖子联系

image.png

2.4 分层模型

image.png

数据层:数据Model,外部数据得增删查改(service层不需要关心具体的底层的数据的存取,它只需要返回一个Model数据)

逻辑层:业务Entity,处理核心业务逻辑输出(通过接受Repository层得数据,做打包封装,会输出一个实体,Entity[话题页面])

视图层,视图view,处理和外部的交互逻辑(对上游负责,包装一些数据格式,格式化一个结果,做一些封装,然后通过API的形式最终访问)

3 组件工具

Gin高性能go web框架

github.com/gin-gonic/g…

Go Mod

go mod init

go get gopkg.in/gin-gonic/gin.v1@v1.3.0

安装过程中的问题:出现cannot find module providing package github.com/gin-gonic/gin: working directory is not part of a module错误

解决方案:

go mod init gin

go mod edit -require github.com/gin-gonic/gin@latest

go mod vendor

5 Repository

Topic

image.png QueryTopicByld

Post

image.png QueryPostsByParentld

5.1 Repository-index

image.png

var (

   topicIndexMap map[int64] *Topic
    
   postIndexMap map[int64][ ] *Post
   
)

实例:初始化话题数据索引

image.png

查询

索引:话题ID

数据:话题

image.png

实现查询:

有了索引直接根据id和t直接读取数据。

sync.Once 关键字:适合在高并发场景下,只执行一次的场景(范例模式),可以减少存储的浪费。

索引:话题ID

数据:帖子列表

然后上送给逻辑层,逻辑层进行一个封装。

6 Service

实体:

image.png

流程:

参数校对-->准备数据-->组装实体

定义实体:命名为PageInfo(页面信息)

有两个实体一个Topic,另一个PostList数组。

service层主要是为了package PageInfo

代码流程编排

image.png

checkParam函数是为了对id进行校验.

prepareInfo实现:获取Responsitory层相应的话题数据和回帖列表数据

image.png

并行处理

image.png

用wg.Wait()进行阻塞,来等待话题信息和回帖信息,从Responsitory层返回。

7 Controller

构建View对象

业务错误码

包装了一个PageData函数,定义了Code、Msg来表示错误得定义。

image.png

8 Router(接口)

初始化数据索引(通过遍历post、ropic文件,生成内存max索引)

初始化引擎配置(Default Inpage)

构建路由(URL路由)

启动服务

image.png

9 运行

运行测试

go run server.go

image.png

10 高质量编程

目标:

编写更简洁清晰的代码

常用Go语言程序优化手段

熟悉Go程序性能分析工具

了解工程中性能优化的原则和流程

10.1 高质量编程简介

定义:编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码。

各种边界条件是否考虑完备

异常情况处理,稳定性保证

易读易维护

10.1.1 编程原则

简单性 :消除“多余的复杂性”,以简单清晰的逻辑编写代码;

不理解的代码无法修复改进。

可读性:代码是写给人看的,而不是机器;

编写可维护代码的第一步是确保代码可读。

生产力:团队整体工作效率非常重要。

10.2 编码规范

代码格式、注释、命令规范、控制流程、错误和异常处理

10.2.0 代码格式

推荐使用gofmt自动格式化代码:常见IDE都支持方便的配置

image.png

goimports 也是,除了格式外,他也能对依赖包进行管理,自动增删依赖的包引用、将依赖包按字母序排列并分类。

10.2.1 注释

作用:

1.解释代码作用;2.解释代码是如何做的;3.解释代码实现的原因;4.解释代码什么情况会出错。

公共符号始终要注释。

image.png

例外:不需要注释实现接口的方法,具体不要像下面这样做:

image.png

10.2.2 命令规范

variable:简洁胜于冗长

缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写

例如:ServeHTTP而不是ServerHttp

使用XMLHTTPRequest或者xmlHTTPRequest

变量距离其被使用地方越远,则需要携带越多的上下文信息。例如全局变量。

image.png

package:

只由小写字母组成。不包含大写字母和下划线等字符

简短并包含一定的上下文信息,例如schema、task等

不要与标准库同名。例如不要使用sync或者strings

10.2.3 控制流程

避免嵌套,保持正常流程清晰

image.png

如果两个分支中都包含return语句,则可以去除冗余的else

尽量保持正常代码路径为最小缩进

优先处理错误情况/特殊情况,尽早返回或继续循环来减少嵌套。

image.png image.png

故障问题大多数出现在复杂的条件语句和循环语句中。

10.2.4 错误和异常处理

简单错误:表示仅仅出现一次的错误,且在其他地方不需要捕获该错误

优先使用errors.New来创建匿名变量来直接表示简单错误。

如果有格式化的需求,使用fmt.Errorf。

image.png

错误的Warp和Unwrap

错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链

在fmt.Errorf中使用:%w关键字来将一个错误关联至错误链中。

image.png

image.png

errors.Is

可以用来判定是否为特定错误

不同于使用==,使用该方法可以判定错误链上的所有是否含有特定的错误

image.png image.png

error.As

在错误链上,获取特定种类的错误,使用errors.As

image.png

panic

不建议在业务代码中使用panic

调用函数不包含recover会造成程序崩溃

若问题可以被屏蔽或解决建议使用error代替panic

当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic image.png

recover

recover只能在被defer的函数中使用

嵌套无法生成

只在当前goroutine生效

defer的语句是后进先出

image.png

小结

image.png

程序会先输出什么?

image.png

defer语句会在函数返回前调用

多个defer语句是后进先出

正确答案是31