Go 工程实践p2 | 青训营笔记

101 阅读3分钟

5. go依赖管理

5.1 GOPATH

  • 环境变量$GOPATH
  • 项目代码直接以来src下代码
  • go get下载新版本包到src目录

image-gopath.png

GOPATH-弊端

  • 多个项目依赖版本不同无法隔离

5.2 Go Vendor

项目目录增加vendor文件,依赖包以副本形式存储在$ProjectRoot/vendor

依赖寻址: vendor->GOPATH

每个项目引入一份依赖副本,解决多个项目需要同一个package依赖的冲突问题

但是无法解决依赖包的版本变通与一个项目需要依赖同一个包不同版本的问题

image-vendor.png

  • 无法控制依赖版本
  • 更新项目可能导致依赖冲突导致编译出错

5.3 Go Module

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

类似maven

依赖配置-go.mod

image-gomod.png

module [xxxxxxxx] 依赖管理基本单元 go [version] 原生库 require( [ModulePath] [Version/Pseudo-version] )

依赖配置-version

语义化版本

MAJOR.{MAJOR}.{MINOR}.${PATCH} v1.3.0 v2.2.4

基于commit伪版本

vX.0.0-yyyymmddhhmmss-dsalkjsdakl1234 v0.1.0-20230102092311-sdac23lkd7

依赖配置-indirect

go.mod中有“// indirect”后缀表示间接依赖

依赖配置-incompatible

对于没有go.mod文件且主版本在2或以上的依赖会加上 +incompatible后缀

5.4 依赖分发

5.4.1回源

image-Origin-pull.png

  • 无法保证构建版本稳定性 增加/修改/删除软件版本
  • 无法保证依赖可用性 删除软件
  • 增加第三方压力 代码托管平台负载问题

5.4.2 Proxy

image-proxy.png

Go Proxy是一个服务站点,缓存源站中的软件内容,缓存的软件版本不会变

使用Go Proxy是从对应站点拉取依赖

变量GOPROXY

可用阿里云的

mirrors.aliyun.com/goproxy/

GOPROXY="proxy1.cn,https://proxy2.cn,…"

依次1、2依赖寻址,都不存在就会从direct即源站下载

5.4.3 工具-go get

go get example.org/pkg @update[默认] @none[删除依赖] @v1.2.1[tag版本] @23kdhas11[特定commit] @master[分支最新的commit]

5.4.4 工具-go mod

go mod [init] (初始化创建go.mod) [download] (下载模块到本地缓存) [tidy] (增加需要的依赖,删除不需要的依赖)

6. 测试

回归测试->集成测试->单元测试 覆盖率增加,成本降低

6.1 单元测试

image-elemtest.png

6.1.1 单元测试规则

  • 所有测试文件以_test.go结尾
  • func TestXxx(t *testing.T)
  • 初始化逻辑放到TestMain的func中

6.1.2 单元测试样例

func HelloTom() string {
	return "Jerry"
}

func TestHelloTom(t *testing.T) {
	output := HelloTom()
	expectOutput := "Tom"
	if output != expectOutput {
		t.Errorf("Expected %s do not match actual %s", expectOutput, output)
	}
}

6.1.3 单元测试-assert

使用assert开源库 github.com/stretchr/testify/assert

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func HelloTom() string {
	return "Jerry"
}

func TestHelloTom(t *testing.T) {
	output := HelloTom()
	expectOutput := "Tom"
	assert.Equal(t, expectOutput, output)
}

6.1.4 覆盖率

judgment.go

func JudgePassLine(score int16) bool {
	if score >= 60 {
		return true
	}
	return false
}

judgment_test.go

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestJudgePassLineTrue(t *testing.T) {
	isPass := JudgePassLine(70)
	assert.Equal(t, true, isPass)
}

result.png

  • 一般覆盖率:50%~60%,较高覆盖率80%+
  • 测试分支相互独立、全面覆盖
  • 测试单元粒度足够小,函数单一负责

6.1.5 单元测试-依赖

image-module.png

外部依赖=>稳定&幂等

6.1.6 单元测试-文件处理

readFirstLine.go

func ReadFirstLine() string {
	open, err := os.Open("log")
	defer open.Close()
	if err != nil {
		return "open error!"
	}
	scanner := bufio.NewScanner(open)
	for scanner.Scan() {
		return scanner.Text()
	}
	return ""
}

func ProcessFirstLine() string {
	line := ReadFirstLine()
	// 仅仅是将文件读入destLine中,不会将更改后的写入文件
	destLine := strings.ReplaceAll(line, "11", "00")
	return destLine
}

readFirstLine_test.go

func TestProcessFirstLine(t *testing.T) {
	firstLine := ProcessFirstLine()
	assert.Equal(t, "line00", firstLine)
}

该单元测试需要依赖本地文件,如果文件被修改或者删除就会fail。

可以对读取文件函数进行mock,屏蔽对文件的依赖

6.1.7 单元测试-mock

使用Monkey 一个开源的mock测试库,可以对method或者实例方法进行mock

bou.ke/monkey

func TestProcessFirstLineWithMock(t *testing.T) {
	monkey.Patch(ReadFirstLine, func() string {
		return "line120"
	})
	defer monkey.Unpatch(ReadFirstLine)
	line := ProcessFirstLine()
	fmt.Println(line)
	assert.Equal(t, "line000", line)
}

6.2 基准测试

initServerIndex.go

var ServerIndex [10]int

func InitServerIndex() {
	for i := 0; i < 10; i++ {
		ServerIndex[i] = i + 100
	}
}

// Select 随机选择执行服务器
func Select() int {
	rand.New(rand.NewSource(time.Now().Unix()))
	return ServerIndex[rand.Intn(10)]
}

initServerIndex_test.go

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()
		}
	})
}

result

test_reult.png