前言
这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记, 第二次课主要讲了GO语言的进阶使用,我针对自己不熟悉的地方做了一些笔记,主要包括Go语言并发,依赖管理,测试等内容
一、Go语言并发
1、协程和线程
- 线程:用户态,轻量级线程,栈MB级别
- 协程:内核态,线程跑多个协程,栈KB级别
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
2、CSP
提倡通过通信共享内存(管道) 而不是通过共享内存通信
3、管道
- 有无缓冲管道
-
- make(chan int)
- 有缓冲管道
-
- make(chan int ,2)
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 {
fmt.Println(i)
}
}
二、依赖管理
参考: segmentfault.com/a/119000002…
module my/thing
go 1.12
require other/thing v1.0.2 // 这是注释
require new/thing/v2 v2.3.4 // indirect
require(
new/thing v2.3.4
old/thing v0.0.0-20190603091049-60506f45cf65
)
exclude old/thing v1.2.3
replace bad/thing v1.4.5 => good/thing v1.4.5
所有前导动词的作用如下:
- module:定义模块路径。
- go:设置预期的语言版本。
- require:要求给定版本或更高版本的特定模块。
- exclude:排除特定版本模块的使用,不允许的模块版本被视为不可用,并且查询无法返回。
- replace:使用不同的模块版本替换原有模块版本。
indirect 注释(第 4 行)标记了该模块不是被当前模块直接导入的,只是被间接导入。
go.mod 文件只存在于在模块的根目录下,子目录中的导入路径会使用模块的导入路径 + 子目录路径的形式。例如:如果创建了一个名叫 world 的子目录,并不需要在子目录中使用 go mod init 命令,Go 命令行工具会自动识别它作为 hello 模块的一部分,所以它的导入路径为 hello/world。
Go 命令行工具会自动处理 go.mod 中指定的模块版本。当源代码中 import 指向的模块不存在于 go.mod 文件中时,Go 命令行工具会自动搜索这个模块,并将最新版本(最后一个 tag 且非预发布的稳定版本)添加到 go.mod 文件中。
如果没有 tag,则使用伪版本(第 7 行),这是一种版本语法,专门用于标记没有 tag 的提交(一些 golang.org/x/ 下的包就是没有 tag 的)。如:v0.0.0-20190603091049-60506f45cf65。
前面部分为语义化版本号,用于标记版本;中间部分为 UTC 的提交时间,用于比较两个伪版本以其确定先后顺序;后面部分是 commit 哈希的前缀,用于标记该版本位于哪个 commit。
三、测试
1、单元测试
- 所有测试文件以_test.go结尾
- func TestXXX( *testing.T)
- 初始化逻辑放在TestMain中
例子
- judgement.go
func JudgePassLine(score int16) bool {
if score >= 60 {
return true
}
return false
}
- judgement_test.go
func TestJudgePassLineTrue(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
func TestJudgePassLineFail(t *testing.T) {
isPass := JudgePassLine(50)
assert.Equal(t, false, isPass)
}
assert
- 可以比较期望结果和真实结果
- go test judgement_test.go judgement.go --cover
func TestJudgePassLineTrue(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
func TestJudgePassLineFail(t *testing.T) {
isPass := JudgePassLine(50)
assert.Equal(t, false, isPass)
}
单元测试依赖
如果有外部依赖如文件、数据库等需要保证稳定性和幂等性
- 幂等:多次请求不变
- 稳定:单元测试相互隔离
单元测试-文件处理
- 如果文件内容发生改变则测试程序会出问题
// 读取文件第一行
func ReadFirstLine() string {
open, err := os.Open("log")
defer open.Close()
if err != nil {
return ""
}
scanner := bufio.NewScanner(open)
for scanner.Scan() {
return scanner.Text()
}
return ""
}
// 替换文件第一行
func ProcessFirstLine() string {
line := ReadFirstLine()
destLine := strings.ReplaceAll(line, "11", "00")
return destLine
}
//文件内容
line10
line22
line33
line44
line55
// test
func TestProcessFirstLine(t *testing.T) {
firstLine := ProcessFirstLine()
assert.Equal(t, "line00", firstLine)
}
单元测试mock机制
快速Mock函数, 通过Mock实现了不对本地文件实现强依赖, 这里是直接给出了依赖文件的函数的返回值
- 为一个函数打桩:用函数b替换函数a
- 为一个方法打桩
func TestProcessFirstLineWithMock(t *testing.T) {
// 直接给出了依赖文件的函数的返回值
monkey.Patch(ReadFirstLine, func() string {
return "line110"
})
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
2、基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
测试普通和快速随机函数,随机选择服务器
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i+100
}
}
// 普通
func Select() int {
return ServerIndex[rand.Intn(10)]
}
// 快速
func FastSelect() int {
return ServerIndex[fastrand.Intn(10)]
}
测试代码
- 测试普通场景下的rand函数效率
- 并发场景下的rand函数效率
- 并发场景fastrand
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Select()
}
}
func BenchmarkSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Select()
}
})
}
func BenchmarkFastSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
FastSelect()
}
})
}