GO依赖管理与测试 | 青训营笔记

55 阅读5分钟

Go进阶依赖

并发与并行

Go可以充分发挥多核优势,高效运行。根据系统的状态分配CPU运行。可以让多线程程序在一个核的CPU上运行达到并发效果,也可以令多线程程序在多个核的CPU运行达到并行效果。

Goroutine

协程:用户态,轻量级线程,栈KB级别

线程:内核态,线程可以并发跑多个协程,栈MB级别

可以说正是因为goroutine的切换代价远远小于线程,不用经过内核态,这是让Go拥有远超别的编程语言的高并发性的重要基石。

CSP

Go的协程不同于Java,C++等采用通过共享内存实现通信,Go的推荐的并发模型为通过通信共享内存,不过也保留了通过共享内存实现通信的方式。

通道为构建一个channel.

make(chan 元素类型,[缓从大小])

make(chan int)//无缓冲通道,会让协程同步
make(chan int,2)//有缓冲通道

Lock

Go的协程实现了并发,但这样的并发顺序不能控制,对于要处理共享内存的并发,需要有并发安全控制,采用sync.Mutex进行加锁处理,实质采用的是sleep这样的一个方法。

var lock sync.Mutex

lock.Lock()
//to do
lock.Unlock()

WaitGroup

计数器,开启协程+1;执行结束协程-1;主协程阻塞直到计数器为0.

//三个公有的方法
Add(delta int)//计数器+delta
Done()//计数器-1
Wait()//阻塞直到计数器为0

开始协程时调用Add,结束时调用Done

func ManyGoWait(){
    var wg sync.WaitGroup
    wg.Add(5)
    for i:=0;i<5;i++{
        go func(j int){
            defer wg.Done()
            hello(j)
        }(i)//(i)直接调用匿名函数执行,传入参数为i
    }
    wg.Wait()
}

依赖管理

GOPATH->GO Vender->Go Module

目前采用最广的是Go Module管理,也是Go语言依赖管理方法迭代至今最新的技术。

  • 通过Go.mod 文件管理依赖包版本
  • 通过go get/go mod指令工具管理依赖包

这样做项目依赖管理的终极目标:定义版本规则和管理项目依赖关系

三要素

  • 配置文件,描述依赖关系定位 go.mod
  • 中心仓库管理依赖库 Proxy
  • 本地工具 go get/mod

依赖配置

主要依赖代码的版本。对于同一模块的不同版本加载时,取兼容版本中最低兼容版本加载

依赖分发

即依赖从哪里下载,如何下载的问题。不直接从平台获取源码,因为不能保证依赖的可用性(没准作者把代码删了哈哈)。

Proxy:服务站点,缓存源站中的内容,版本也不会改动。稳定且可靠

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

对已有的缓存进行查找,均查找不到后,回源进入direct即第三方源站查找

依赖工具

go get example.org/pkg + 参数

参数:

@update 默认

@none 删除依赖

@v1.1.2 tag版本,语义版本

@23dfdd5 特定的commit

@master 分支的最新commit

个人感觉他跟git的项目管理基本一致。

go mod + 指令

指令:

init 初始化,创建go.mod文件

download 下载模块到本地缓存

tidy 增加需要的依赖,删除不需要的依赖

测试

  • 回归测试:模拟用户使用产品,进行测试
  • 集成测试:系统功能的自动化测试等
  • 单元测试:对单独的函数或者模块进行测试

单元测试

规则

  • 所有测试文件以_test.go结尾。开发时就不要这么命名啦!!

  • 测试函数命名:func TestXxx(t *testing.T)

  • 初始化逻辑放到TestMain中

实际代码

//开发函数
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)
    }
}

也可以用assert进行测试,用法如下:

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

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

依赖

测试开发追求幂等稳定,即要重复运行结果一致和单元测试独立运行。验证代码的正确性和独立性。

依赖有文件,数据库,缓存等,,此时可以采用Mock进行测试

常用的Mock组件包:https://github.com/bouk/monkey

Mock即是用一个函数(方法)替换另一个函数(方法)。打桩

实现:在运行测试函数时,将原函数地址替换成打桩函数地址。

func TestProcessFirstLineWithMock(t *testing.T){
    monkey.Patch(ReadFirstLine,func() string{
        return "line110"
    })//替换ReadFirstLine函数为打桩函数
    defer monkey.Unpatch(ReadFirstLine)//删除打桩函数
    line := ProcessFirstLine()
    assert.Equal(t,"line000",line)
}

上述Mock方法即实现了原本对有文件读入依赖的ReadFirstLine函数替换,去除对文件依赖,直接测试ProcessFirstLine函数

基准测试

用于代码性能测试,瓶颈分析。

例子是对一个含rand随机的函数做测试

//基准测试函数名以Benchmark开头
func BenchmarkSelect(b *testing.B){
    b.ResetTimer()//重置时间
    for i:=0;i<b.N;i++{
        Select()
    }
}
func BenchmarkSelectParallel(b *testing.B){
    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB){//并行测试
        for pb.Next(){
            Select()
        }
    })
}

b.N代表用例中测试代码的循环次数,首先从1开始,能在1s内完成,则go test会逐渐增加b.N再次运行

go test命令的一些参数:

  • -run regex:运行regex所匹配的所有单元测试
  • -bench regex:运行regex所匹配的所有基准测试
  • -cpu:设置执行测试的CPU数量,以逗号分割
  • -benchmem:输出内存分配情况
  • -count:设置执行轮次
  • -benchtime:设置执行时间,默认1s

学习感想

对Go的依赖管理和测试的学习很明显的丰富了我学习Go语言的眼界,还帮我解释了在Go学习中的bug调试方法,以后对测试开发岗也不是一窍不通了,大致知道了测试的过程,以及其追求的目标是什么。同样的方法对开发时的问题也会有很大的帮助。