Go的工程与进阶 | 青训营笔记

196 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

Go进阶

并发vs并行

Go 可以充分发挥多核优势,高效运行

Goroutine

Goroutine启用简单,使用go func(){}就能启动

CSP(Communicating Squential Processes)

Go协程使用的是通信共享内存,即左图所示,使用的是Channel通道的形式来,保证go协程之间的先入先出。右图使用的是共享内存的形式进行通信,其缺点是需要加锁来保证不出现竞态,效率较低。

channel

make(chan int)无缓冲通道 make(chan int,2 )有缓冲通道 这里dest使用的是有缓冲的通道的原因,消费者的速度较慢,生产者速度快,所以带缓冲就不会影响生产者的执行效率。

并发安全lock

WaitGroup

waitgroup可以保证主协程和其他协程之间能正常退出。

Go依赖管理

Go的依赖管理有三个阶段Gopath GovendorGomodule 目的

  • 不同环境依赖的版本不同
  • 控制依赖库的版本

Gopath

Gopath 优点是很方便的将所有包都统一管理 缺点:

Go Vendor

Go Module

  • 通过go.mod文件管理依赖包版本
  • 通过go get/go mod指令工具管理依赖包
  1. 配置文件,依赖描述 go.mod
  2. 中心仓库管理依赖库 Proxy
  3. 本地工具 go get/mod

indirect 表示模块的间接依赖

  • 主版本2+模块会在模块路径增加/VN 后缀。
  • 对于没有 go.mod 文件并且主版本2+的依赖,会+incompatible

Proxy使用的出现

  • 无法保证构建稳定性增加/修改/删除软件版本

  • 无法保证依赖可用性删除软件

  • 增加第三方压力代码托管平台负载问题

go get使用 go mod 使用

GO测试

测试的重要性,保证不发生事故 测试有三种

  • 回归测试
  • 集成测试
  • 单元测试

单元测试

单元测试规则

  • 所有测试文件以_test.go 结尾
  • 测试函数为func TestXxx(*testing.T)
  • 初始化逻辑放到 TestMain 中 Example
#hello.go
package unit_test

func HelloTom() string {
	return "Tom"
}
#hellov1_test.go
package unit_test

import "testing"

func TestHelloTom(t *testing.T) {
	output := HelloTom()

	expectOutput := "Tom"
	if output != expectOutput {
		t.Errorf("Expected %s do not match actual %s", expectOutput, output)
	}
}

单元测试覆盖率

  • 如何衡量代码是否经过了足够的测试?

  • 如何评价项目的测试水准?

  • 如何评估项目是否达到了高水准测试等级?

代码覆盖率 在终端中输入 go test hello_test.go hello.go --cover 可以查看测试代码和源代码的覆盖率

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

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

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

单元测试-依赖

单元测试需要稳定性和幂等性,即每次测试结果要一样,各函数之间要求隔离。

mock example func Patch为一个函数打桩 func Unpatch为一个函数卸桩

# deal.go
func ReadFirstLine() string {
	open, err := os.Open("log")
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
	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
}
#deal_test.go
func TestProcessFirstLineWithMock(t *testing.T) {
	monkey.Patch(ReadFirstLine, func() string {
		return "line110"
	})
	defer monkey.Unpatch(ReadFirstLine)
	line := ProcessFirstLine()
	assert.Equal(t, "line000", line)
}

对ReadFirstLine打桩测试,不再依赖本地文件。我们使用了 monkey.Patch() 方法来替换ReadFirstLine 函数,在测试用例中调用 ReadFirstLine 函数时,实际上调用了我们mocking的函数.在测试结束后使用 monkey.Unpatch() 方法恢复原来的函数。 注意: 使用monkey进行单元测试不是很推荐,因为它会更改了函数的行为,而不是只是测试函数的输入输出,这样的话会导致更多.

总结

在本次学习中学习到了goroutine的使用包括通道、锁和waitgroup等方法来控制资源竞争。 单元测试是生命线,通过单元测试可以防患于未然,有效减少项目上线时出错的概率。 mock包能去除掉测试函数与其他文件资源的耦合。