这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
课程内容
- 语言进阶
- 依赖管理
- 测试
语言进阶
Goroutine-协程
GO语言可以充分发挥多核优势,提高运行效率,防止出现所谓“一核有难 N核围观”的情况。
这要部分归功于Goroutine 也就是 协程;按课程所讲,线程运行在内核态下,一个线程跑多个协程,栈属于MB级别,而协程运行在用户态下,属于轻量级的线程,栈属于KB级别。
通过 go 关键字即可开启,例如:
package concurrence
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
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()
}
这就是一个启动多个协程快速打印hello的例子,其中Add()是增加计数器,Done()是减少,Wait()是挂起直到计数器归0,这是sync中WaitGroup的函数,它在其中维护了一个计数器.
CSP(Communicating Sequential Processes)
Go提倡:通过通信达到共享内存的效果,而不是通过共享内存达到通信效果;
Channel-通道
图中的通道就是 channel 它是用来传递数据的一个数据结构,用于在两个 goroutine 之间传递一个指定类型的值来实现同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
channel <- value // 把 v 发送到通道 ch
value := <-channel // 从 ch 接收数据
// 并把值赋给 v
通过make即可声明一个channel:
make(chan 元素类型,[缓冲大小])
缓冲大小可以和括号可以不填,这样就是无缓冲的channel,通过close()关闭channel。
Lock-锁
当然,go也保留了通过共享内存实现通信的方式,那就意味着需要对互斥区进行上锁:
package concurrence
import "sync"
var (
x int64
lock sync.Mutex
)
func addWithLock(){
for i := 0 ; i < 2000 ; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
依赖管理
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
go.mod
课程中还提到了,如果一个项目依赖的其中两个模块各依赖一个模块的高低版本,会选择能兼容这两个版本的最低模块。
Proxy
实际就是通过依赖分发来实现稳定可靠的下载源
go get/mod
- go mod 命令
| 命令 | 作用 |
|---|---|
| go mod init | 生成go.mod 文件 |
| go mod download | 下载 go.mod 文件中指明的所有依赖 |
| go mod tidy | 整理现有的依赖 (拉取缺少的模块,移除不用的模块) |
| go mod graph | 查看现有的依赖结构 |
| go mod edit | 编辑 go.mod 文件 |
| go mod vendor | 将依赖复制到vendor目录下 |
| go mod verify | 校验一个模块是否被篡改过 |
| go mod why | 查看为什么需要依赖某模块 |
其中go mod download 拉起的模块,结果缓存在 $GOPATH/pkg/mod和 $GOPATH/pkg/sumdb 目录下
- go install = 用来编译安装本地项目
- go get = 下载 Go 包 + go install
当然我一般会用git clone 来拉去GitHub上的项目.
其中go get格式为:
go get {模块名}@{最新版本: latest|分支: master|tag: v0.3.2|hash: 342b2e}
测试
概要
测试是很重要的一环,是开发和正式上线中间的最后一道屏障.测试从上到下分为回归测试,集成测试,单元测试,覆盖率逐层提高,成步却逐步降低.所以单元测试是十分重要的.
单元测试规则
Go语言中的测试依赖go test命令,此命令是一个按照一定约定和组织的测试代码的驱动程序。
- 测试文件以_test.go为结尾
- 测试函数在原函数名称基础前加上Test,参数为t *testing.T
- 初始化逻辑放入TestMain() 中
单元测试运行
*_test.go 文件 包含三种类型函数,分别是单元测试,基准测试,示例测试函数
| 类型 | 前缀格式 | 作用 |
|---|---|---|
| 基础测试 | Test | 测试程序的逻辑是否正确 |
| 性能测试 | Benchmark | 测试程序的性能 |
| 示例测试 | Example | 为程序提供示例 |
go test 命令的格式为:
go test [-c] [-i] [build flags] [packages] [flags for test binary]
- -c : 编译go test成为可执行的二进制文件,但是不运行测试。
- -i : 安装测试包依赖的package,但是不运行测试。
- build flags:调用go help build查询相关,这些是编译运行过程中需要使用到的参数,一般设置为空
- packages:调用go help packages查询相关,这些是关于包的管理,一般设置为空
- flags for test binary: 有很多,详细的可去查询文档
Mock-打桩测试
因为有些函数会对文件进行增删,为了防止对本地文件形成依赖而对函数或者模块使用的测试方法.
工程中的测试
- 一般覆盖率在 50%~60% ,较高的会达到80% 此时成本也很大
- 测试分支相互独立,全面覆盖
- 测试单元粒度足够小,函数单一负责
个人总结
此次课程贴近工程实践内容,前面的协程和通道讲述了Go的并发编程的基础;中间的依赖管理描述了GO项目工程中经常用到的版本和依赖管理,类似Java中的Maven一样,Go也有专属的依赖管理系统;后面的测试栏目讲解了工程实践中重要的部分,大概介绍了单元测试和基准测试的使用方式以及在工程中的作用,使我受益匪浅。