这是我参与「第五届青训营」伴学笔记创作活动的第2天。今天的课程主题是go语言的进阶学习,主要包括3块内容:语言进阶、工程版本管理和测试。
1 语言进阶
go非常适用于高并发场景。
1.1 协程 Goroutine
并发:多个线程在1核cpu上运行
并行:多个线程在多核cpu上运行
协程属于用户态,比进程(内核态)更轻量级。
创建协程关键字:go
func ManyGo() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
1.2 CSP (communicating sequential processes)
Go提倡通过通信共享内存。相对而言,通过共享内存进行多线程访问对效率的影响更大。
通过通信的表现形式为通道,通道为单向所以不存在在通道内访问冲突的问题。
通道使用关键字 make
来创建。
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
1.3 Lock和WaitGroup
Sync包主要包含一些并发安全操作和协程间同步操作。
Lock即锁
func AddWithLock() {
cnt = 0
for i := 0; i < 5; i++ {
lock.Lock()
cnt += 1
lock.Unlock()
}
}
WaitGroup主要用于计数。主要包含3个方法:Add(delta)给计数器+delta,Done给计数器-1,Wait等待计数器到0。
func ManyGo() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
2 依赖管理
2.1 发展历程
GOPATH -> Go Vendor -> Go Module
GOPATH涉及环境变量定义,核心的3个文件夹为bin,pkg和src。所以依赖都在src下。
弊端:无法实现package的多版本控制
Go Vendor添加了Vendor文件夹,所以依赖包以副本形式存在其中。依赖的寻址先看Vendor再看GOPATH。解决了依赖冲突的问题。
弊端:无法标志依赖版本
Go Module通过go.mod文件管理依赖包版本(1.16版本开始自动开启)。通过go get/go mod指令工具管理依赖包。
终极目标:定义版本规则和项目依赖关系。
2.2 依赖管理三要素
配置文件、描述依赖 -> go.mod
中心仓库管理依赖库 -> Proxy
本地工具 -> go get/mod
2.3 go.mod文件解读
第一部分module为依赖管理基本单元。第二部分指出go版本,即原生库,第三行require为单元依赖,包括路径和版本。
module github.com/Moonlight-Zhao/go-project-example
go 1.16
require (
gopkg.in/gin-gonic/gin.v1 v1.3.0
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.3.0 // indirect
}
依赖配置的版本规则:语义化版本和基于commit的伪版本。
语义化版本形式为 ${MAJOR}.${MINOR}.${PATCH}。
indirect关键字表示间接依赖,incompatible用于没有go.mod文件且主版本在v2及以上的依赖(可能存在不兼容情况)。
出现依赖同一个库不同版本时,会自动选择最低的兼容版本。
2.4 Proxy
Proxy用于缓存依赖,避免源码被修改等问题。(没有什么问题是1个proxy解决不了的,如果有,就2个)
变量GOPROXY是一个字符串,存储依赖的路径列表,direct表示源站。
2.5 工具 go get 和 go mod
go get example.org/pkg
后接各种命令。
go mod init
初始化,创建go.mod文件
go mod download
下载模块到本地缓存
go mod tidy
增加需要的依赖,删除不需要的依赖
3 测试
测试的3种主要类型:回归测试(手动)、集成测试(功能)、单元测试(测试覆盖率逐步递增,消耗逐步递减)
3.1 单元测试
3.1.1 单元测试的编写
测试文件以 "_test.go" 结尾。
测试方法以 "func TestXxx(t *testing.T)" 开头。
初始化逻辑放在 TestMain 方法中。
可以引用assert等包进行测试。
3.1.2 单元测试的评估与设计
单元测试的评估:代码覆盖率。可以使用 "go test XXX.go --cover" 查看代码覆盖率(行数比例)。
一般覆盖率50% - 60%,较高覆盖率为80%+。
测试的设计:测试分支相互独立、全面覆盖,测试单元粒度足够小,函数单一职责。
3.1.3 单元测试的依赖
测试的目标:幂等、稳定
3.2 Mock测试
常用测试包:monkey(用于给函数或方法打桩)
3.3 基础测试
主要用于测试代码性能。
4 总结
今天的内容是基于了解语法的扩展。第一部分语法进阶涉及的协程主要是代码深度的提高。第二部分工程项目的配置主要是代码量的增加和文件版本间的统一。第三部分测试是软件开发周期中的重要组成部分。