Go进阶-工程实践2 | 青训营笔记

170 阅读4分钟

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

3. 测试

3.1 单元测试

单元测试主要包括、输入、测试单元、输出以及校对,单元的概念比较广,包括接口、函数、模块等;用最后的校对来保证代码的功能与我们的预期相符:

  1. 单测一方面可以保证质量,在整体覆盖率足够的情况下,一定程度上既保证了新功能本身的正确性,又未破坏原有代码的正确性。

  2. 另一方面可以提升效率,在代码有bug的情况下,通过编写单测,可以在一个较短周期内定位和修复问题。

3.1.1 规则
  1. 所有测试文件都以_test.go结尾

    image-20230117211654885

  2. func TestXxx(t *testing.T)

    image-20230117211736761

  3. 初始化逻辑放到 TestMain

    image-20230117213220135

3.1.2 例子
// hello.go文件中的函数
func HelloTom() string {
    // 这里模拟因为业务上的错误,导致最后输出的是错误结果
    return "helloJerry"
}

// hello_test.go文件中对应的测试函数
func TestHelloTom() {
    // 接收目标测试函数返回的结果
    output := HelloTom()
    // 期待目标函数返回的结果
    expect := "helloTom"
    // 如果两者不匹配,直接抛错,并且打印出来这两个结果
    if output != expect {
        t.Errorf("不匹配,output:%v,expect:%v", output, expect)
    }
}
3.1.3 运行

运行测试函数的方法有几种,

  1. 方式一(goland):

image-20230117215202920

注意要选中目标文件和测试文件两个,如果只选中一个Run不会有这种选项。

  1. 方式二(Terminal):
// 测试文件内的所有函数
go test [flags] [packages]
// 想只测试文件中的一个函数
// 前提是当前位置是文件所在的文件夹位置
go test -v -run [函数名称]

image-20230117215811504

image-20230117214909374

这里肯定是测试不通过的,因为两个结果不同,如果把目标函数中的返回结果改正,那么

image-20230117215901181

3.1.4 assert

不用多写代码的测试比较工具——assert(断言)

// 安装工具包
go get -i github.com/stretchr/testify/assert

image-20230117220319302

3.1.5 覆盖率

目前我们了解了如何单元测试,那么如何衡量代码是否经过了足够的测试?如何评价项目的测试水准? 如何评估项目是否达到了高水准测试等级?(致命三连😆)————那就是代码覆盖率

image-20230117220902643

image-20230117220920158

image-20230117221144424

嗯哼,解释一下,我这里这么低的覆盖率,是因为还有很多函数没有测试到,我只测试了 TestScoreCheck 这一个函数,如果你文件中只有这一个函数,覆盖率应该是100%

但是如果 ScoreCheck中是

if score >= 60 {
    return true
}
return false

那么覆盖率应该是 66.7%,其实覆盖率就是当前所举用例可以执行目标函数的代码行数的占比

image-20230117222027630

再加个用例,覆盖率就达到百分之百了。

tips:

  • 一般的要求是50%~60%覆盖率,而对于资金型服务,覆盖率可能要求达到80%+
  • 我们做单元测试,测试分支相互独立、全面覆盖
  • 要求函数体足够小,这样就比较简单的提升覆盖率,也符合函数设计的单一职责

3.2 文件处理

// read.go中的目标函数
func ReadFirstLine() string {
    // 操作文件,可能需要提前在文件中添加一写数据
    // line11
    // line22
    // line33
    // line44
    open, err := os.Open("log.txt")
    // 最后关闭文件,释放资源
    defer open.Close()
    // 如果有异常,返回空字符串
    if err != nil {
        return ""
    }
    // 创建扫描器
    scanner := bufio.NewScanner(open)
    for scanner.Scan() {
        return scanner.Text()
    }
    return ""
}
func ProcessFirstLine() string {
    // 读取第一行数据
	line := ReadFirstLine()
    // 将数据中的“11”全部替换为“00”
	destLine := strings.ReplaceAll(line, "11", "00")
    // 返回修改后的数据
	return destLine
}

// read_test.go中的测试函数
func TestProcess(t *testing.T) {
    firstLine := ProcessFirstLine()
	assert.Equal(t, "line00", firstLine)
}

image-20230117223233564

3.3 Mock

这里我们用了Monkey,monkey是一个开源的mock测试库,可以对method,或者实例的方法进行mock, 反射,指针赋值。Mockey Patch的作用域在Runtime,在运行时通过通过Go的unsafe包,能够将内存中函数的地址替换为运行时函数的地址,将待打桩函数或方法的实现跳转到。

// 下载monkey包
go get -i github.com/bouk/monkey
func TestWithMock(t *testing.T) {
    // 对ReadFirstLine打桩测试,不再依赖本地文件
    monkey.Patch(ReadFirstLine, func() string {
        return "line110"
    })
    // 释放资源
    defer monkey.Unpatch(ReadFirstLine)
    line := ProcessFirstLine()
    // 比较结果
    assert.Equal(t, "line000", line)
}

3.4 基准测试

Go 语言还提供了基准测试框架,基准测试是指测试一段程序的运行性能及耗费CPU的程度,而我们在实际项目开发中,经常会遇到代码性能瓶颈,为了定位问题经常要对代码做性能分析,这就用到了基准测试,使用方法类似于单元测试,

3.4.1 例子
// jizhun.go文件函数详情
// 模拟随机选择执行服务器
import (
    "math/rand"
)
// 创建服务器数组
var ServerIndex [10]int
// 初始化服务器中的数据
func InitServerIndex() {
    for i := 0; i < 10; i++ {
        ServerIndex[i] = i + 100
    }
}
// 模拟随机选择服务器
func Select() int {
    return ServerIndex[rand.Intn(10)]
}
// jizhun_test.go文件函数详情
// 基准测试为benchmark,所以是*testing.B
func BenchmarkSelect(b *testing.B) {
    // 初始化数据
	InitServerIndex()
    // 重置因为初始化耗费的时间,这些操作不应该作为基准测试的范围
	b.ResetTimer()
    // 对一个测试用例的默认测试时间是1秒,当测试用例函数返回时还不到1秒,那么 testing.B中的N值将按1、2、5、10、20、50...递增,并以递增后的值重新进行用例函数测试
	for i := 0; i < b.N; i++ {
		Select()
	}
}
// runparallel是多协程并发测试,执行2个基准测试,发现代码在并发情况下存在劣化,主要原因是rand为了保证全局的随机性和并发安全,持有了一把全局锁。
func BenchmarkSelectParallel(b *testing.B) {
    // 初始化
	InitServerIndex()
    // 重置时间
	b.ResetTimer()
    // 多协程并发测试
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			Select()
		}
	})
}

image-20230117225445580

3.4.2 优化

image-20230117225617960

为了解决这一随机性能问题,字节开源了一个高性能脑加数方法fastrand,再做一下基准测试,性能提升了百倍。主要的思路是牺牲了一定的数列一致性,在大多数场合是适合的,如果在后面遇到随机的场景可以尝试用一下。

go get -i github.com/bytedance/gopkg

image-20230117225930669

未完待续。。。