Go语言工程实践之测试 | 青训营笔记

语言: CN / TW / HK

theme: nico

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

前言

记录加入青训营的每一天的日笔记

背景

测试的出现是为了避免项目中出现重大事故

测试是避免事故的最后一道屏障

测试

image.png

单元测试的覆盖率在一定程度上而言,决定了代码的质量

单元测试

image.png

通过测试单元的输出与期望值进行校对从而验证代码的正确性,从而保证新旧代码的互不影响与程序的正常运行。

进而单元测试较于编译更易于在较短的周期内发现和定位代码中的错误使损失最小化从而提升效率。所以写单元测试是很有必要的。

Golang单元测试对文件名和方法名,参数都有很严格的要求

  • 文件名必须以xx_test.go命名
  • 方法必须是Test[^a-z]开头
  • 方法参数必须t *testing.T
  • 初始化逻辑放到TestMain中
  • 使用go test执行单元测试

演示

通过第三方包assert演示单元测试

判断函数测试值与期望值是否一致

import( "github.com/stretchr/testify/assert" "testing" ) ​ func TestHelloTom(t *testing.T) {    output := HelloTom()    expectOutput := "Tom"    assert.Equal(t, expectOutput, output) } ​ func HelloTom() string {    return "Tom" }

覆盖率

覆盖率出现的目的:

  • 衡量代码是否经过了足够的测试
  • 评价项目的测试水准
  • 评估项目是否达到了高水准测试等级

通过go test命令测试函数的覆盖率

// judgment.go func JudgePassLine(score int16) bool {    if score >= 60 {        return true   }    else {        return false   } } ​ // judgment_test.go func TestJudgePassLineTrue(t *testing.T) {    isPass := JudgeePassLine(70)    assert.Equal(t, true, isPass) } ​ func TestJudgePassLineFalse(t *testing.T) {    isPass := JudgeePassLine(50)    assert.Equal(t, false, isPass) } ​ /* 通过go test 命令测试覆盖率 go test judgment_test.go judgment.go --cover */

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

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

    对于上述案例代码而言

    应出现成绩大于等于60 和小于60的测试用力

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

依赖

image.png

  • 幂等:重复运行同一个case,结果与之前一致
  • 稳定:指单元测试相互隔离,可以独立运行

文件处理

当测试文件被修改后,可能会导致测试失败或错误率增高

从而出现了Mock函数

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") // 替换11为00 return destLine } ​ func TestProcessFirstLine(t *testing.T) { // 执行单元测试 firstLine := ProcessFirstLine() assert.Equal(t, "line00", firstLine) }

Mock

monkey: https://github.com/bouk/monkey 这是一个开源的mock测试库,可以对method或者实例的方法进行mock

Monkey Patch的作用域在Runtime, 运行时通过Go的unsafe包能够将内存中函数的地址替换为运行时函数的地址,将待打桩函数或方法的实现跳转。

Mock函数不仅可以为一个函数打桩 也可以为一个方法打桩

// 用函数A去替换函数B,B就是原函数,A就是打桩函数 ​ func Patch(target, replacement interface{}) *PatchGuard {    // target就是原函数,replacement就是打桩函数 t := reflect.ValueOf(target) r := reflect.ValueOf(replacement) patchValue(t, r) return &PatchGuard{t, r} } ​ func Unpatch(target interface{}) bool {    // 保证了在测试结束之后需要把这个包卸载掉 return unpatchValue(reflect.ValueOf(target)) } ​ func TestProcessFirstLineWithMock(t *testing.T) { monkey.Patch(ReadFirstLine, func() string { return "line110" }) defer monkey.Unpatch(ReadFirstLine) line := ProcessFirstLine() assert.Equal(t, "line000", line) } // 通过patch对ReadFirstLine进行打桩mock,默认返回line110,通过defer卸载mock // 这样整个测试函数就摆脱了本地文件的束缚和依赖

基准测试

基准测试是指测试一段程序的性能及耗费CPU的程度;

在实际的项目开发中,经常会遇到代码性能瓶颈,为了定位问题,经常要对代码做性能分;

这时就用到了基准测试,其使用方法与单元测试类似。

  • 优化代码,需要对当前代码分析
  • 内置的测试框架提供了基准测试的能力

小结

对于今日课程而言,我将其划分成测试的重要性与分类。 当前课程余下部分为项目实战,该部分内容选择了放置于项目笔记。 如果笔记中有错误的地方也希望掘友们可以及时的提出纠正。