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

前言

本文从单元测验实践角度动身,进步对代码质量的认识。

本文内容首要包含:单元测验、Mock测验、基准测验。

测验

测验可以进步代码的质量、削减事故的产生。

测验又分为:回归测验、集成测验、单元测验。

回归测验是指对QA手动回归一些特定场景,可以理解为咱们说的手动点点。

集成测验是指对体系功用维度做验证,比方对服务露出的接口验证,一般是自动化的验证。

单元测验是指在开发阶段,开发者对独自的函数、模块做验证,写一些测验用例。

Go 语言进阶——单元测试| 青训营笔记

单元测验

单元测验组成部分:输入、输出、测验单元、与希望的校正,测验单元又包含函数、接口、模块、复杂的聚合函数等。

经过单元测验的输出再与希望输出进行校正,来验证代码的正确性。经过单元测验可以保证代码的质量,也可以在必定程度上进步功率,比方经过运转单元测验可以快速定位到有问题的代码。

Go 语言进阶——单元测试| 青训营笔记

规矩

单元测验的编写有必定的规矩:

  1. 一切测验文件以 _test.go 结束
  2. 测验办法名以Test最初,参数要用testing func TestXxx(t *testing.T)
  3. 测验初始化逻辑放到TestMain
  4. 经过go 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)
    }
}
func HelloTom() string {
    return "Jerry"
}

经过go test指令运转得到以下成果,从测验成果里可以看出,咱们希望得到的是Tom,但实践得到的却是Jerry

--- FAIL: TestHelloTom (0.00s)
helloTom_test.go:9: Expected Tom do not match actual Jerry
FAIL
exit status 1
FAIL    learning/mytesting      0.496s

assert

别的咱们可以使用开源的assert包,来代替咱们自己的if判断。

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

再次经过go test指令运转得到以下成果,输出了更详细的堆栈信息。

Go 语言进阶——单元测试| 青训营笔记

掩盖率

如何评估单元测验呢?是经过代码掩盖率来评估的,评估的规范包含:

  1. 衡量代码是否经过了满足的测验
  2. 评价项目的测验水准
  3. 评估项目是否到达了高水平的测验等级

下面经过一个例子来看下代码掩盖率:

// judgepass.go
func JudgePassLine(score int16) bool {
    if score >= 16 {
        return true
    }
    return false
}
// judgepass_test.go
func TestJudgePassLineTrue(t *testing.T) {
    isPass := JudgePassLine(70)
    assert.Equal(t, true, isPass)
}

经过指令来看掩盖率go test judgepass_test.go judgepass.go --cover

输出成果为:

ok  command-line-arguments  0.327s  coverage: 66.7% of statements

可以看到,提示出的代码掩盖率为66.7%,由于只走了一个if分支。假如要想到达100%的代码掩盖率的话,就要把一切的分支都要掩盖到。

一般的掩盖率为50%~60%,较高的掩盖率要到达80%+。要注意:测验分支相互独立、要全面掩盖,测验单元粒度要满足小,满足函数单一职责。

依靠

一个实践项目不可能只是一个简略的单体函数,肯定会很复杂,存在其他的依靠,比方依靠数据库、redis、文件等外部依靠。

单元测验一般有两个目标:幂等、安稳。

幂等:重复履行一个用例、调用一个接口,回来的成果是相同的。

安稳:单元测验是相互阻隔的,在任何时间都能独立运转。

Go 语言进阶——单元测试| 青训营笔记

Mock

假如单元测验用到数据库、redis等,在单元测验里直接衔接会涉及到网络传输,这是不安稳的,所以要用到Mock机制。

开源Mock结构:github.com/bouk/monkey

这个Mock包可以对函数或办法进行打桩,打桩便是用一个函数A来替换一个函数B。

monkey的完成原理首要是在运转时,经过Gounsafe包能够将内存中函数的地址替换为运转时函数的地址,最终调用的是打桩函数,从而完成Mock的功用。

Mock常用办法:PatchUnpatch

Patch办法有两个参数,target为替换的函数(原函数),replacement为要替换成的函数。

func Patch(target, replacement interface{}) *PatchGuard {
    t := reflect.ValueOf(target)
    r := reflect.ValueOf(replacement)
    patchValue(t, r)
    return &PatchGuard{t, r}
}

Unpatch为测验结束之后,要把打的桩给卸载掉。

func Unpatch(target interface{}) bool {
    return unpatchValue(reflect.ValueOf(target))
}

下面经过Mock来模仿对文件的操作。

func TestProcessFirstLineWithMock(t *testing.T) {
    monkey.Patch(ReadFirstLine, func() string {
        return "line110"
    })
    defer monkey.Unpatch(ReadFirstLine)
    line := ProcessFirstLine()
    assert.Equal(t, "line000", line)
}
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
}

该测验用例对ProcessFirstLine函数进行测验,这个函数调用了ReadFirstLine函数,涉及到文件的操作,经过Mock对文件的操作进行打桩,这样就避免了其他进程对文件操作的影响。

基准测验

Go还供给了基准测验结构,可以测验一段程序的功能、CPU耗费,可以对代码做功能剖析,测验办法与单元测验相似。

基准测验规矩:

  • 基准测验以Benchmark为前缀
  • 需求一个*testing.B类型的参数b
  • 基准测验必需要履行b.N次

下面经过一个模仿负载均衡的例子,来看下基准测验:

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

经过指令 go test -bench=. 运转测验,输出成果如下:

goos: darwin
goarch: amd64
pkg: learning/bench
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.70GHz
BenchmarkSelect-8               50264580               23.47 ns/op
BenchmarkSelectParallel-8       13717840               133.4 ns/op
PASS
ok      learning/bench  4.559s

BenchmarkSelect-8 表明对Select函数进行基准测验,数字8表明 GOMAXPROCS 的值。

23.47 ns/op 表明每次调用Select函数耗时23.47ns

50264580 这是50264580次调用的平均值。

字节开源的go结构:github.com/bytedance/g…

引证

Go 语言进阶与依靠办理