七叶笔记 » golang编程 » 大白话 golang 教程-07-单元测试和重构

大白话 golang 教程-07-单元测试和重构

在前面的例子中,我们需要依靠在 main 函数中调用包内的函数,根据 fmt.Println 来确定函数是不是按照预期的工作,这非常的不方便,因为函数可能是包内小写的私有函数,它并不需要暴露到外面,而且函数可能压根不需要输出任何东西,它只要把数据处理好就行了,所以得学会怎么测试代码的正确性。

把上个章节的 incUpdateScore 函数复制一下,放到 varpointer 包下面取名 memaddr.go,内容如下:

 func incUpdateScore(ptrScore *int) {
  *ptrScore += 2
}  

同样的目录下新建一个 memaddr_test.go 的文件,注意以 _test 结尾,输入以下内容:

 func Test_incUpdateScore(t *testing.T) {
  score := 10
  expected := 11
  incUpdateScore(&score)

  if score != expected {
    t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
  }
}  

注意函数名称以 Test 开头,参数类型是 *testing.T,在函数名称上出现了一排工具栏,点 run test 后,输出如下:

 --- FAIL: Test_incUpdateScore (0.00s)
    /Users/wangbo/Desktop/go-demo/07-code-testing/varpointer/memaddr_test.go:11: 有问题, 期望 11, 实际是 12
FAIL
FAIL  07-code-testing/varpointer  0.465s
FAIL  

测试失败了,期待加上 1,结果加了2,修复代码:

 func incUpdateScore(ptrScore *int) {
  *ptrScore++
}  

现在结果显示:

 ok    07-code-testing/varpointer  0.785s  

说明函数测试通过了,在终端 shell 使用 go test -v ./… 依然显示测试通过:

 07-code-testing[master*]  go test -v ./...
?   07-code-testing[no test files]
=== RUN   Test_incUpdateScore
11
--- PASS: Test_incUpdateScore (0.00s)
PASS
ok  07-code-testing/varpointer0.341s  

小结一下,测试包内的函数步骤:

1. 新建一个 xx_test.go 的文件

2. 新建函数 Test_funcName,它的参数 t *testing.T

3. 编写测试代码

4. 使用 go test 命令来运行测试文件

现在我们来对 incUpdateScore 进行重构,我们希望递增的分数是以参数的形式传进去:

 func incUpdateScore(ptrScore *int, increment int) {
  *ptrScore += increment
}  

保存文件后发现 vscode 自动运行了测试,并且报告了测试失败:

 # 07-code-testing/varpointer [07-code-testing/varpointer.test]
/Users/wangbo/Desktop/go-demo/07-code-testing/varpointer/memaddr_test.go:8:16: not enough arguments in call to incUpdateScore
  have (*int)
  want (*int, int)
FAIL  07-code-testing/varpointer [build failed]
FAIL  

编译都没有通过,因为我们修改了函数签名,但是还没有修改测试代码,重构测试代码如下:

 func Test_incUpdateScore(t *testing.T) {
  score := 10
  expected := 11
  incUpdateScore(&score, 1)

  if score != expected {
    t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
  }
}  

保存后看到测试通过了,传入正数函数工作正常,我们想测试一下负数的情况,使用 t.Run 来包装 2 个测试场景,继续重构为 2 个子测试代码如下:

 func Test_incUpdateScore(t *testing.T) {
  t.Run("should increment score by + number", func(t *testing.T)  {
    score := 10
    expected := 11
    incUpdateScore(&score, 1)

    if score != expected {
      t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
    }
  })

  t.Run("should increment score by - number", func(t *testing.T)  {
    score := 10
    expected := 9
    incUpdateScore(&score, -1)

    if score != expected {
      t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
    }
  })
}  

再次运行测试也通过了,说明函数 incUpdateScore 具有预期的行为,这个过程我没有没有借助 main 函数和 fmt 包就完成了测试和重构。

 07-code-testing[master*]  go test -v ./...
?   07-code-testing[no test files]
=== RUN   Test_incUpdateScore
=== RUN   Test_incUpdateScore/should_increment_score_by_+_number
=== RUN   Test_incUpdateScore/should_increment_score_by_-_number
--- PASS: Test_incUpdateScore (0.00s)
    --- PASS: Test_incUpdateScore/should_increment_score_by_+_number (0.00s)
    --- PASS: Test_incUpdateScore/should_increment_score_by_-_number (0.00s)
PASS
ok  07-code-testing/varpointer0.559s  

但是这两个测试场景有一些重复性的代码,有点让人不爽对不对,函数就是消除重复代码的好办法,我们再次重构一下。

 func Test_incUpdateScore(t *testing.T) {
  checkEqual := func(t *testing.T, expected, got int) {
    t.Helper() // 出错的时候报告准确位置
    if expected != got {
      t.Errorf("有问题, 期望 %d, 实际是 %d", expected, got)
    }
  }

  t.Run("should increment score by + number", func(t *testing.T) {
    score := 10
    incUpdateScore(&score, 1)
    checkEqual(t, 11, score)
  })

  t.Run("should increment score by - number", func(t *testing.T) {
    score := 10
    incUpdateScore(&score, -1)
    checkEqual(t, 9, score)
  })
}  

刚才创建的测试是单元测试 (*testing.T),go test 还支持基准测试 (*testing.B),自定义入口测试 (*testing.M),以后的章节会经常用到测试,需要掌握它。

本章节的代码

相关文章