GO语言进阶--并发、依赖管理、测试 | 青训营笔记

132 阅读4分钟

前言

这是我参与「第三届青训营 -后端场」笔记创作活动的的第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()
		}
	})
}