Go单元测试

167 阅读6分钟

一、单元测试用例编写规则

  1. 测试文件的名称必须以**_test.go
  2. 测试函数必须和被测试的方法在同一个包下
  3. 测试函数的传参必须是*testing.T类型
  4. 测试函数的函数名称必须以Test****开头,且Test后的第一个字母为除不可为小写字母外的任何字符串,如大写字母、下划线_、数字

go build 不会编译测试文件

二、testing.T类型中的常用函数

  • 处理失败事件的方法
    • 测试失败,打印出失败信息后,测试继续
      • func (c *T) Error(args ...interface{})
      • func (c *T) Errorf(format string, args ...interface{})
    • 测试失败,不输出打印信息,测试继续
      • func (c *T) Fail()
    • 测试失败,不输出打印信息,测试中断
      • func (c *T) FailNow()
    • 测试失败,中断测试,打印出信息
      • func (c *T) Fatal(args ...interface{})
      • func (c *T) Fatalf(format string, args ...interface{})
  • 处理日志
    • 输出日志,对于测试流程没有影响,但是需要留意,在单元测试中,测试成功时,它们打印的信息不会输出,可以通过加上 -v 选项,输出这些信息。测试失败时,如果执行的流程中存在该方法,会进行输出。
      • func (c *T) Log(args ...interface{})
      • func (c *T) Logf(format string, args ...interface{}
  • 跳出测试
    • 跳过测试,测试中断
      • func (c *T) SkipNow()
    • 跳过测试,又输出传入的参数,功能上相当于t.Log和t.SkipNow()的集合
      • func (c *T) Skip(args ...interface{})
      • func (c *T) Skipf(format string, args ...interface{})

三、单元测试用例

创建ex_test.go文件,输入以下内容:

package UnitTest

import "testing"

func plus(a, b int) int {
	return a + b
}

func Test_Plus(t *testing.T) {
	var obj = struct {
		val1 int
		val2 int
		sum  int
	}{
		2, 3, 9,
	}
	t.Log("start.....")
        if obj.val1+obj.val2 == obj.sum {
		t.Logf("execute success:%d plus %d equals %d", obj.val1, obj.val2, obj.sum)
	} else {
		t.Errorf("execute failed: %d plus %d is not equal to %d ", obj.val1, obj.val2, obj.sum)
	}

    t.Skip("execute  done!")
	t.Skipped()
}

通过命令 go test ex_test.go执行代码,输出:

ok command-line-arguments	0.007s

执行命令go test -v ex_test.go ,输出:

=== RUN   Test_Plus
--- SKIP: Test_Plus (0.00s)
   ex_test.go:17: start.....
   ex_test.go:19: execute success:2 plus 3 equals 5
   ex_test.go:24: execute  done!
PASS
ok  	command-line-arguments	0.007s

修改测试用例为:

var obj = struct {
	val1 int
	val2 int
	sum  int
}{
	2, 3, 9,
}

执行 go test ex_test.go命令输出:

--- FAIL: Test_Plus (0.00s)
    ex_test.go:17: start.....
    ex_test.go:21: execute failed: 2 plus 3 is not equal to 9
    ex_test.go:24: execute  done!
FAIL
FAIL	command-line-arguments	0.007s

基于以上测试发现,方法t.Skip() 、t.Skipf()具有 与t.Log()、t.Logf()一样的特征: 在单元测试中,测试成功时,它们打印的信息不会输出,需要通过加上 -v 选项,输出这些信息。测试失败时,如果执行的流程中存在该方法,会进行输出。

四、go test 常用命令参数

go语言通过go test 命令执行参数,

  • -v 输出测试的详细信息

  • -run 指定测试函数 例如:-run "TestA" 、-run="TestA" ,在函数名称中包含TestA的测试函数都会被执行,TestA可以包含在名称中的最前边,中间或结尾

  • -timeout 本次执行所用时间不可超时,否则会执行panic,默认是10s 例如:-timeout 1m30s

  • -count 设置测试执行的次数,默认值为1

  • -args 命令行参数

  • -parallel 多个测试函数并行执行,默认值为GOMAXPROCS 例如:-parallel 2

    GOMAXPROCS一般指cpu核数

  • branch

测试特定文件或方法

  • 测试特定文件 : 需要在命令行输入被测试的原文件

    go test XXX_test.go XXX.go

  • 测试特定方法

    go test -v -test.run TestRefreshAccessToken

    go test -v file_test.go -test.run TestFunc

五、Parallel

在同一个测试文件中,如果测试方法中不包含Parallel()方法,且测试文件中同时包含多个测试函数,那么测试函数按照顺序输出。

在parallel_test.go测试文件中输入以下代码,

package UnitTest

import (
	"testing"
	"time"
)

func Test_A(t *testing.T) {
	time.Sleep(1 * time.Second)
	t.Log("sleep 1 second .....")
}

func Test_B(t *testing.T) {
	t.Log("test b ......")
}

执行命令 go test -v parallel_test.go,输出:

=== RUN   Test_A
--- PASS: Test_A (1.00s)
    parallel_test.go:10: sleep 1 second .....
=== RUN   Test_B
--- PASS: Test_B (0.00s)
    parallel_test.go:15: test b ......
PASS
ok  	command-line-arguments	1.010s

在上面的测试函数中添加方法t.Parallel():

package UnitTest

import (
	"testing"
	"time"
)

func Test_A(t *testing.T) {
	t.Parallel()
	time.Sleep(1 * time.Second)
	t.Log("sleep 1 second .....")
}

func Test_B(t *testing.T) {
	t.Parallel()
	t.Log("test b ......")
}

执行命令 go test -v parallel_test.go,输出:

=== RUN   Test_A
=== PAUSE Test_A
=== RUN   Test_B
=== PAUSE Test_B
=== CONT  Test_A
=== CONT  Test_B
--- PASS: Test_B (0.00s)
    parallel_test.go:17: test b ......
--- PASS: Test_A (1.00s)
   parallel_test.go:11: sleep 1 second .....
PASS
ok  	command-line-arguments	1.012s

基于以上测试,Parallel()方法用于并行执行包含Parallel()方法的测试函数

六、子测试

从Go1.7开始,

在 run_test.go 文件中输入以下代码:

package UnitTest

import "testing"

func Test_A1(t *testing.T) {
	t.Run("TestA", func(t *testing.T) {
		t.Log("Test A")
	})
	t.Run("TestB", func(t *testing.T) {
		t.Log("Test B")
	})
	t.Run("TestAB", func(t *testing.T) {
		t.Log("Test AB")
	})
}

func Test_B1(t *testing.T) {
	t.Run("TestA", func(t *testing.T) {
		t.Log("Test A")
	})
	t.Run("TestB", func(t *testing.T) {
		t.Log("Test B")
	})
	t.Run("TestAB", func(t *testing.T) {
		t.Log("Test AB")
	})
}

func Test_C1(t *testing.T) {
	t.Run("TestGroup", func(t *testing.T) {
		t.Run("TestGroup_A", func(t *testing.T) {
			t.Log("TestGroup A")
		})
		t.Run("TestGroup_B", func(t *testing.T) {
			t.Log("TestGroup AB")
		})
	})
}
  • go test -v -run '' run_test.go 执行文件中的所以测试函数,和go test -v run_test.go 结果一样

  • go test -v -run Test_A/TestA run_test.go 执行测试函数中顶层函数包含Test_A的函数里面名字包含TestA的子测试函数

      === RUN   Test_A1
      === RUN   Test_A1/TestA
      === RUN   Test_A1/TestAB
      --- PASS: Test_A1 (0.00s)
          --- PASS: Test_A1/TestA (0.00s)
              run_test.go:7: Test A
          --- PASS: Test_A1/TestAB (0.00s)
              run_test.go:13: Test AB
      PASS
      ok  	command-line-arguments	0.007s
    
  • go test -v -run /Group/Group_A run_test.go 子测试的函数可以是多层包含,该名称查找所有顶层测试函数中子测试名称包含Group的函数,再查找该子测试下的子测试名称包含Group_A的测试名称进行测试

      === RUN   Test_A1
      --- PASS: Test_A1 (0.00s)
      === RUN   Test_B1
      --- PASS: Test_B1 (0.00s)
      === RUN   Test_C1
      === RUN   Test_C1/TestGroup
      === RUN   Test_C1/TestGroup/TestGroup_A
      --- PASS: Test_C1 (0.00s)
          --- PASS: Test_C1/TestGroup (0.00s)
              --- PASS: Test_C1/TestGroup/TestGroup_A (0.00s)
                  run_test.go:32: TestGroup A
      PASS
      ok  	command-line-arguments	0.009s
    
  • go test -v -run test_/Group/Group_A run_test.go
    执行结果:

      testing: warning: no tests to run
      PASS
      ok  	command-line-arguments	0.007s [no tests to run]
    

    -run 的名称匹配原则是根据 正则表达式,区分字母的大小写

根据上面的测试结果可以发现,执行测试命令时,go test 会遍历执行文件下所有的测试方法,找到方法名称和 -run 后面参数匹配的方法进行执行。

七、TestMain

八、例代码 Example

1.例代码 编写规则

  1. 例代码文件名称以 _test.go 结尾
  2. 例代码的方法必须以Example开头,且Example后的第一个字母为除不可为小写字母外的任何字符串,如大写字母、下划线_、数字

2.例子

在e_test.go文件中输入以下代码:

    package TestM
    
    import "fmt"
    
    func add(a, b int32) int32 {
    	return a + b
    }
    
    func Example_Add() {
    	fmt.Println(add(2, 3))
    	fmt.Println(add(9, 3))
    	// Output:
    	// 5
    	// 12
    }

文件中必须包含Output注释,fmt.Println(add(2, 3))和fmt.Println(add(9, 3)) 的执行结果会和Output后的信息进行匹配,如果相同,就执行成功。

执行:go test e_test.go输出:

ok  	command-line-arguments	0.007s  

如果如果将Output部分的值修改,则

// Output:
// 5
// 122

执行go test e_test.go,输出:

--- FAIL: Example_Add (0.00s)
got:
5
12
want:
5
122
FAIL
FAIL	command-line-arguments	0.007s

两个fmt.Println的输出结果分别与output后的值进行匹配,必须全部匹配相同才为执行成功,或者均失败报错。

对for循环中输出进行匹配,用UnOrdered Output: 例如:

func Example_Map() {
	var map_test map[int]string = make(map[int]string)
	map_test[1] = "first"
	map_test[2] = "second"
	map_test[3] = "three"
	map_test[4] = "four"

	for _, v := range map_test {
		fmt.Println(v)
	}
	// UnOrdered Output:
	// first
	// second
	// three
	// four
}

func (c *T) Helper()

func (c *T) Name() string

func (c *T) Failed() bool

func (c *T) Skipped() bool

-run

参数-run对应一个正则表达式,只有测试函数名被它正确匹配的测试函数才会被go test测试命令运行: $ go test -v -run="TestTwo"