单元:一个函数

  1. func add(n int) int {
  2. res := 0
  3. for i := 0; i < n; i++ {
  4. res += i
  5. }
  6. return res
  7. }

怎么测试上面函数的正确性呢?

传统方法进行测试

在main函数中调用 add 函数,看看实际输出跟预期是否一致,如果一致,说明函数正确,否则说明有误。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func add(n int) int {
  6. res := 0
  7. for i := 0; i < n; i++ {
  8. res += i
  9. }
  10. return res
  11. }
  12. func main() {
  13. var sum int = add(5)
  14. if sum != 10 {
  15. fmt.Printf("add函数错误, 返回值:%v, 期望值: %v \n", sum, 10)
  16. } else {
  17. fmt.Printf("add函数正确, 返回值:%v, 期望值: %v \n", sum, 10)
  18. }
  19. }

缺点分析

  1. 不方便,需要在主函数中调用,这样就需要修改代码,如果项目正在运行,可能还需要停止项目
  2. 不利于管理,当需要测试多个函数或者多个模块时,都需要写在main函数,容易扰乱代码逻辑

    单元测试

    基本介绍

    Go语言自带有一个轻量级测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试。
    可以基于 testing 框架写针对相应函数写测试用例,也可以写压力测试用例。

  3. 通过单元测试,可以解决以下问题:

  4. 确保每个函数是可运行的,并且运行结果是正确的
  5. 确保写出来的代码性能是好的
  6. 单元测试能及时发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题定位解决,让程序能在高并发的情况下保持稳定

import “testing”

testing 提供对 Go 包的自动化测试的支持。通过 **go test** 命令,能够自动执行如下形式的任何函数:
func TestXxx(testing.T)
其中 Xxx 可以是任何字母数字字符串(但第一个字母不能是 [a-z]),用于识别测试例程。
在这些函数中,使用 Error, Fail 或相关方法来发出失败信号。
要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx 函数,如上所述。 将该文件放在与被测试的包相同的包中。该文件将被排除在正常的程序包之外,但在运行 “go test” 命令时将被包含。 有关详细信息,请运行 “go help test” 和 “go help testflag” 了解。
如果有需要,可以调用
T 和 *B 的 Skip 方法,跳过该测试或基准测试:

  1. func TestTimeConsuming(t *testing.T) {
  2. if testing.Short() {
  3. t.Skip("skipping test in short mode.")
  4. }
  5. ...
  6. }

func (c *T) FailNow()

当前测试标识为失败并停止执行该测试,在此之后,测试过程将在下一个测试或者下一个基准测试中继续。
FailNow 必须在运行测试函数或者基准测试函数的 goroutine 中调用,而不能在测试期间创建的 goroutine 中调用。调用 FailNow 不会导致其他 goroutine 停止。

func (c *T) Fatalf(format string, args …interface{})

调用 Fatalf 相当于在调用 Logf 之后调用 FailNow 。

func (c *T) Logf(format string, args …interface{})

Log 使用与 Printf 相同的格式化语法对它的参数进行格式化,然后将格式化后的文本记录到错误日志里面。

代码测试

testcase/cal.go 文件

  1. // 关于计算的测试用例
  2. package main
  3. func add(n int) int {
  4. res := 0
  5. for i := 0; i < n; i++ {
  6. res += i
  7. }
  8. return res
  9. }
  10. func getSub(n1 int, n2 int) int {
  11. return n1 - n2
  12. }

testcase/cal_test.go

  1. package main
  2. import (
  3. "testing" // 引入 testing 框架
  4. )
  5. // 编写测试用例
  6. func TestAdd(t *testing.T) {
  7. // 调用
  8. res := add(5)
  9. if res != 10 {
  10. // fmt.Printf("add(5)执行错误, 返回值:%v, 期望值: %v \n", res, 10)
  11. t.Fatalf("add(5)执行错误, 返回值:%v, 期望值: %v \n", res, 10)
  12. } else {
  13. t.Logf("add(5)执行正确")
  14. }
  15. }
  16. func TestHello(t *testing.T) {
  17. // fmt.Printf("t类型: %T, 值: %v \n", t, t)
  18. t.Logf("TestHello被调用")
  19. }
  20. /*
  21. go test -v
  22. === RUN TestAdd
  23. cal_test.go:16: add(5)执行正确
  24. --- PASS: TestAdd (0.00s)
  25. === RUN TestHello
  26. cal_test.go:22: TestHello被调用
  27. --- PASS: TestHello (0.00s)
  28. PASS
  29. ok go_code/unittest/testdemo01/testcase 6.356s
  30. */

testcase/sub_test.go

  1. package main
  2. import "testing"
  3. // 编写测试用例
  4. func TestGetSub(t *testing.T) {
  5. // 调用
  6. res := getSub(10, 3)
  7. if res != 7 {
  8. // fmt.Printf("add(5)执行错误, 返回值:%v, 期望值: %v \n", res, 10)
  9. t.Fatalf("getSub(10, 3)执行错误, 返回值:%v, 期望值: %v \n", res, 7)
  10. } else {
  11. t.Logf("getSub(10, 3)执行正确")
  12. }
  13. }

测试结果

  1. === RUN TestAdd
  2. cal_test.go:16: add(5)执行正确
  3. --- PASS: TestAdd (0.00s)
  4. === RUN TestHello
  5. cal_test.go:22: TestHello被调用
  6. --- PASS: TestHello (0.00s)
  7. === RUN TestGetSub
  8. sub_test.go:14: getSub(10, 3)执行正确
  9. --- PASS: TestGetSub (0.00s)
  10. PASS
  11. ok go_code/unittest/testdemo01/testcase 10.916s

testing框架调用说明

image.png

注意事项

  1. 测试用例文件名必须以 xxx_test.go 命名(比如: cal_test.go)
  2. 测试用例函数名必须是 TestXxx ,testing框架内部会自动收集并调用这些函数(比如: TestAdd)
  3. TestXxx 函数的参数类型必须是 t *testing.T (比如: TestAdd(t *testing.T)
  4. 一个测试用例文件中可以包含多个测试用例函数
  5. go test 命令: 如果测试通过则没有log输出(不输出 t.Logf t.Fatalf ,只输出 pass 和运行时间),错误的情况下才会有log
  6. go test -v 会输出log
  7. 测试用例函数并没有放在main函数中
  8. PASS 表示运行成功, FAIL 表示运行失败
  9. 测试单个用例文件,一定要带上被测试的源文件 (比如: go test -v cal_test.go cal.go)
  10. 测试单个用例函数,(比如: go test -v -run TestAdd)