推荐使用TDD(Test Driver Development),然而现在很多人都不注重测试。

go test工具

Go语言中依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的。

go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀的源代码文件都是go test测试的一部分,不会被go build编译到最终可执行文件。

*_go test文件中有三种类型的函数,单元测试函数,基准测试函数和实力函数。

类型 格式 作用
测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为Benchmark 测试函数的性能
示例函数 函数名前缀为Example 为文档提供示例文档

go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行,报告测试结果,最后清理测试中生成的临时文件。

测试函数

测试函数的格式

每个测试函数必须导入testing包,测试函数的基本格式(签名)如下。

1
2
3
func TestName(t *testing.T){
// ...
}

测试函数的名字开头必须以Test开头,可选的后缀名必须以大写字母开头。

1
2
3
func TestAdd(t *testing.T){...}
func TestSum(t *testing.T){...}
func TestLog(t *testing.T){...}

其中参数t用于报告测试失败和附加的日志信息。testing.T拥有的方法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (t *T) Parallel()
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool

测试函数示例

就像细胞是构成我们身体的基本单位,一个软件程序也是由很多单元组件构成。单元组件可以是函数,结构体,方法,和最终用户可以依赖的任何东西。总之我们需要确保这些组件是能够正常运行的。单元测试是一些利用各种方法测试单元组件的程序,它会将结果与预期结果进行比较。

以下面含函数为例,自定义一个split包,包中定义一个Split函数。

1
2
3
4
5
6
7
8
9
10
11
func Split(s, sep string) (result []string) {
i := strings.Index(s, sep)

for i > -1 {
result = append(result, s[:i])
s = s[i+1:]
i = strings.Index(s, sep)
}
result = append(result, s)
return
}

在当前目录下创建一个split_test.go的测试文件,并定义一个测试函数:

1
2
3
4
5
6
7
8
9
10
11
12
//测试函数名必须以Test开头,必须接收一个*testing.T类型的参数
func TestSplit(t *testing.T) {
//程序输出的结果
got := Split("a:b:c", ":")
//期望的结果
want := []string{"a", "b" ,"c"}
//因为slice不能直接比较,借助反射包中的方法进行比较
if !reflect.DeepEqual(want, got) {
t.Errorf("excepted:%v, got:%v", want, got)
}
}

split包路径下执行go test命令,得到输出结果:

1
2
3
05split>go test
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 0.623s

创建更多的测试测试字符串切割函数:

1
2
3
4
5
6
7
func TestSplit2(t *testing.T) {
got := Split("abcd", "bc")
want := []string{"a", "d"}
if !reflect.DeepEqual(want, got) {
t.Errorf("excepted:%v, got:%v", want, got)
}
}

再次运行go test命令,输出结果如下:

1
2
3
4
5
6
05split>go test
--- FAIL: TestSplit2 (0.00s)
split_test.go:24: excepted:[a d], got:[a cd]
FAIL
exit status 1
FAIL github.com/DurianLollipop/StudyGo/day08/05split 0.546s

使用go test命令添加-v参数,查看测试函数名称和运行时间,可以清楚的看到所有测试执行的测试的结果:

1
2
3
4
5
6
7
8
9
05split>go test -v
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
=== RUN TestSplit2
TestSplit2: split_test.go:24: excepted:[a d], got:[a cd]
--- FAIL: TestSplit2 (0.00s)
FAIL
exit status 1
FAIL github.com/DurianLollipop/StudyGo/day08/05split 0.506s

go test命令后添加-run参数,它对应一个正则表达式,只有函数名称匹配上的测试函数才会被go test命令执行。

1
2
3
4
5
6
7
05split>go test -run="TestSplit2" -v
=== RUN TestSplit2
TestSplit2: split_test.go:24: excepted:[a d], got:[a cd]
--- FAIL: TestSplit2 (0.00s)
FAIL
exit status 1
FAIL github.com/DurianLollipop/StudyGo/day08/05split 0.517s

针对程序中出现的问题进行修复:

1
2
3
4
5
6
7
8
9
10
11
12
func Split(s, sep string) (result []string) {
i := strings.Index(s, sep)

for i > -1 {
result = append(result, s[:i])
//使用len(sep)计算下一次起始位置
s = s[i+len(sep):]
i = strings.Index(s, sep)
}
result = append(result, s)
return
}

在修复完测试之后再执行一遍涉及到的所有测试,以确保没有引入新的问题。

1
2
3
4
5
6
7
05split>go test -v
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
=== RUN TestSplit2
--- PASS: TestSplit2 (0.00s)
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 0.497s

测试组

如果现在我们还想测试一下split函数对中文字符的支持,我们还可以编写一个TestChinesSplit测试函数,但是可以使用更加友好的方式添加更多的测试用例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func TestSplit3(t *testing.T) {
//自定义测试用例类型
type test struct {
input string
sep string
want []string
}

//定义一个储存测试用例的切片
tests := []test{
{input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
{input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
{input: "abcd", sep: "bc", want: []string{"a", "d"}},
{input: "上海自来水来自海上", sep: "水", want: []string{"上海自来", "来自海上"}},
}

//遍历切片,执行所有的测试用例
for _, tc := range tests{
got := Split(tc.input, tc.sep)
if !reflect.DeepEqual(tc.want, got) {
t.Errorf("excepted:%v, got:%v", tc.want, got)
}
}
}

执行该测试组

1
2
3
4
5
6
7
05split>go test -run="TestSplit3" -v
=== RUN TestSplit3
TestSplit3: split_group_test.go:28: excepted:[海自来 来自海], got:[ 海自来 来自海 ]
--- FAIL: TestSplit3 (0.00s)
FAIL
exit status 1
FAIL github.com/DurianLollipop/StudyGo/day08/05split 0.486s

使用%#v打印测试失败信息能更好的发现错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func TestSplit3(t *testing.T) {
//自定义测试用例类型
type test struct {
input string
sep string
want []string
}

//定义一个储存测试用例的切片
tests := []test{
{input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
{input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
{input: "abcd", sep: "bc", want: []string{"a", "d"}},
{input: "上海自来上来自海上", sep: "上", want: []string{"海自来", "来自海"}},
}

//遍历切片,执行所有的测试用例
for _, tc := range tests{
got := Split(tc.input, tc.sep)
if !reflect.DeepEqual(tc.want, got) {
t.Errorf("excepted:%#v, got:%#v", tc.want, got)
}
}
}

控制台输出:

1
2
3
4
5
6
7
05split>go test -run="TestSplit3" -v
=== RUN TestSplit3
TestSplit3: split_group_test.go:28: excepted:[]string{"海自来", "来自海"}, got:[]string{"", "海自来", "来自海", ""}
--- FAIL: TestSplit3 (0.00s)
FAIL
exit status 1
FAIL github.com/DurianLollipop/StudyGo/day08/05split 0.491s

子测试

当测试用例过多时,是没办法看出具体是哪一个测试用例失败,使用Go1.7+中新加的子测试可以解决该问题,使用t.Run()执行子测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func TestSplit4(t *testing.T) {
//自定义测试用例类型
type test struct {
input string
sep string
want []string
}

//定义一个储存测试用例的切片
tests := map[string]test{
"case_1" : {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
"case_2" : {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
"case_3" : {input: "abcd", sep: "bc", want: []string{"a", "d"}},
"case_4" : {input: "上海自来上来自海上", sep: "上", want: []string{"海自来", "来自海"}},
}

//遍历切片,执行所有的测试用例
for name, tc := range tests{
t.Run(name, func(t *testing.T) {
got := Split(tc.input, tc.sep)
if !reflect.DeepEqual(tc.want, got) {
t.Errorf("excepted:%#v, got:%#v", tc.want, got)
}
})
}
}

控制台输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
05split>go test -run="TestSplit4" -v
=== RUN TestSplit4
=== RUN TestSplit4/case_1
=== RUN TestSplit4/case_2
=== RUN TestSplit4/case_3
=== RUN TestSplit4/case_4
TestSplit4/case_4: split_sub_test.go:29: excepted:[]string{"海自来", "来自海"}, got:[]string{"", "海自来", "来自海", ""}
--- FAIL: TestSplit4 (0.00s)
--- PASS: TestSplit4/case_1 (0.00s)
--- PASS: TestSplit4/case_2 (0.00s)
--- PASS: TestSplit4/case_3 (0.00s)
--- FAIL: TestSplit4/case_4 (0.00s)
FAIL
exit status 1
FAIL github.com/DurianLollipop/StudyGo/day08/05split 0.429s

可以通过-run=RegExp来指定运行测试用例,还可以通过/来指定要运行的子测试,例如go test -run=TestSplit4/case_3 -v

1
2
3
4
5
6
7
05split>go test -run=TestSplit4/case_3 -v
=== RUN TestSplit4
=== RUN TestSplit4/case_3
--- PASS: TestSplit4 (0.00s)
--- PASS: TestSplit4/case_3 (0.00s)
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 0.643s

测试覆盖率

测试覆盖率是指你的代码被单元测试覆盖的百分比,我们通常使用的是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比率。

Go提供内置的功能来检查代码的覆盖率,使用go test -cover来查看测试的覆盖率。

1
2
3
4
05split>go test -cover
PASS
coverage: 100.0% of statements
ok github.com/DurianLollipop/StudyGo/day08/05split 0.535s

以上结果表明测试覆盖率达到100%。

Go语言还提供了额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件。

1
2
3
4
05split>go test -cover -coverprofile=cover.out
PASS
coverage: 100.0% of statements
ok github.com/DurianLollipop/StudyGo/day08/05split 0.689s

执行go tool cover -html=cover.out,使用cover工具处理生成的记录信息,该命令会打开本地浏览器窗口生成一个HTML报告。

记录信息

绿色标记的语句块表示被覆盖了,而红色的表示没有被覆盖。

基准测试

基准测试的函数格式

基准测试就是在一定的工作负载下检测程序性能的一种方法,基准测试的基本格式如下:

1
2
3
func BenchmarkNmae(b *testing.B){
// ...
}

基准测试以Benchmark为前缀,需要一个b *testing.B类型的参数b,基准测试必须要执行b.N次,这样测试才有对照性,b.N的值是系统根据实际情况去调整的,保证测试的稳定性。testing.B拥有的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fail()
func (c *B) FailNow()
func (c *B) Failed() bool
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Name() string
func (b *B) ReportAllocs()
func (b *B) ResetTimer()
func (b *B) Run(name string, f func(b *B)) bool
func (b *B) RunParallel(body func(*PB))
func (b *B) SetBytes(n int64)
func (b *B) SetParallelism(p int)
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow()
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped() bool
func (b *B) StartTimer()
func (b *B) StopTimer()

基准测试示例

以split包中的Split函数编写基准测试:

1
2
3
4
5
func BenchmarkSplit(b *testing.B) {
for i := 0; i < b.N; i++ {
Split("a:b:c", ":")
}
}

基准测试并不会默认执行,需要增加-bench参数,通过go test -bench=Split命令执行基准测试,输出结果如下:

1
2
3
4
5
6
7
05split>go test -bench=Split
goos: windows
goarch: amd64
pkg: github.com/DurianLollipop/StudyGo/day08/05split
BenchmarkSplit-8 5603208 219 ns/op
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 2.000s

其中BenchmarkSplit-8表示对Split函数进行基准测试,数字8表示GOMAXPROCS的值,这个对于并发基准测试很重要。5603208219 ns/op表示每次调用Split函数耗时219 ns,这个结果是5603208次调用的平均值。

使用-benchmem参数,来获得内存分配的统计数据。

1
2
3
4
5
6
7
05split>go test -bench=Split -benchmem
goos: windows
goarch: amd64
pkg: github.com/DurianLollipop/StudyGo/day08/05split
BenchmarkSplit-8 5334916 222 ns/op 112 B/op 3 allocs/op
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 2.002s

其中112 B/op表示每次操作内存分配了112字节,3 allocs/op表示每次操作进行了3次内存分配。

Split函数优化:

1
2
3
4
5
6
7
8
9
10
11
12
func Split(s, sep string) (result []string) {
result = make([]string, 0, strings.Count(s, sep)+1)
i := strings.Index(s, sep)
for i > -1 {
result = append(result, s[:i])
//使用len(sep)计算下一次起始位置
s = s[i+len(sep):]
i = strings.Index(s, sep)
}
result = append(result, s)
return
}

提前使用make函数将result初始化为一个容量够大大的切片,而不是之前的通过append函数来追加。

控制台输出:

1
2
3
4
5
6
7
05split>go test -bench=Split -benchmem
goos: windows
goarch: amd64
pkg: github.com/DurianLollipop/StudyGo/day08/05split
BenchmarkSplit-8 11383266 105 ns/op 48 B/op 1 allocs/op
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 1.846s

使用make函数提前分配内存的改动,减少了2/3的内存分配次数,并减少了一般的内存分配。

性能比较函数

上面的基准测试只能得到给定操作的绝对耗时,但是在很多性能问题发生在两个不同操作之间的相对耗时,如同一个函数处理1000个元素与处理10000个甚至100万个元素的耗时差别是多少?再或者对于同一个任务究竟使用那种算法性能最佳?我们通常要对两个不同算法的实现使用相同的输入进行基准比较测试。

性能比较测试通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用。

以测试斐波那契数列为例:

1
2
3
4
5
6
func Fib(n int) int{
if n < 2 {
return n
}
return Fib(n-1) * Fib(n-2)
}

编写性能比较函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func benchmarkFib(b *testing.B, size int){
for i := 0; i < b.N; i++ {
Fib(size)
}
}

func BenchmarkFib1(b *testing.B){
benchmarkFib(b, 1)
}

func BenchmarkFib2(b *testing.B){
benchmarkFib(b, 2)
}

func BenchmarkFib10(b *testing.B){
benchmarkFib(b, 10)
}

func BenchmarkFib20(b *testing.B){
benchmarkFib(b, 10)
}

运行基准测试:

-bench=.代表运行该包的所有的性能基准测试

1
2
3
4
5
6
7
8
9
10
05fibonaccinumber>go test -bench=.
goos: windows
goarch: amd64
pkg: github.com/DurianLollipop/StudyGo/day08/05fibonaccinumber
BenchmarkFib1-8 537147558 1.97 ns/op
BenchmarkFib2-8 210452324 5.58 ns/op
BenchmarkFib10-8 3283648 357 ns/op
BenchmarkFib20-8 27069 45764 ns/op
PASS
ok github.com/DurianLollipop/StudyGo/day08/05fibonaccinumber 6.791s

重置时间

b.ResetTimer之前的处理不会放到执行时间里,也不会输出到报告中,所以可以在之前做一些不计划作为测试报告的操作,例如:

1
2
3
4
5
6
7
8
func BenchmarkSplit2(b *testing.B) {
time.Sleep(5 * time.Second)
//重新记录时间
b.ResetTimer()
for i := 0; i < b.N; i++ {
Split("a:b:c", ":")
}
}

并行测试

func (b *B) RunParallel(body func(*PB))会以并行的方式执行给定的基准测试。

RunParallel会创建出多个goroutine,并将b.N分配给这些goroutine执行,其中goroutine数量的默认值为GOMAXPROCS。如果用户想要增加非CPU受限(non-CPU-bound)基准测试的并行性,那么可以在RunParallel之前调用SetPararllelismRunParallel通常会与-cpu标志一同使用。

1
2
3
4
5
6
7
8
9
func BenchmarkSplitWithParallel(b *testing.B) {
//设置使用的CPU数
b.SetParallelism(4)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Split("a:b:c", ":")
}
})
}

执行基准测试:

1
2
3
4
5
6
7
8
9
05split>go test -bench=.
goos: windows
goarch: amd64
pkg: github.com/DurianLollipop/StudyGo/day08/05split
BenchmarkSplit-8 10462840 107 ns/op
BenchmarkSplit2-8 10110516 107 ns/op
BenchmarkSplitWithParallel-8 39056408 31.1 ns/op
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 29.771s

还可以通过在测试命令后面添加-cpu参数如go test -bench=. -cpu 1来指定使用的CPU数量。

1
2
3
4
5
6
7
8
9
05split>go test -bench=SplitWithParallel
-cpu 4
goos: windows
goarch: amd64
pkg: github.com/DurianLollipop/StudyGo/day08/05split
BenchmarkSplitWithParallel-4 32248549 32.0 ns/op
PASS
ok github.com/DurianLollipop/StudyGo/day08/05split 2.049s

SetUp与TearDown

测试程序有时候需要在测试之前进行额外的设置(setUp)或在测试之后进行拆卸(tearDown)操作。

TestMain

如果测试文件包含函数func TestMain(m testint.M)那么生成的测试会先执行TestMain(m),然后再运行具体测试。TestMain运行在主goroutine中,可以在调用m.Run前做任何设置(setUp)和卸载(tearDown)。退出测试的时候应该使用m.Run的返回值作为调用参数调用os.Exit

如何使用TestMain来设置SetUp和TearDown:

1
2
3
4
5
6
7
8
9
10
11
func TestMain(m *testing.M){
//测试之前做的一些设置
fmt.Println("write setUp code here...")
//如果 TestMain 使用了flags,这里应该加上flag.Parse()
//执行测试
resCode := m.Run()
//测试之后的一些拆卸工作
fmt.Println("write teardown code here...")
//退出测试
os.Exit(resCode)
}

需要注意的是:在调用TestMain时,flag.Parse并没有被调用。所以如果TestMian依赖于command-line标志(包括testing包的标记),则应该显示的调用flag.Parse

子测试的SetUp和TearDown

有时候我们可能需要为每个测试集设置SetUp与TearDown,也有可能需要为每个子测试设置SetUp与TearDown。

定义两个工具函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//测试集的setup与teardown
func setupTestCase(t *testing.T) func(t *testing.T) {
t.Log("如有需要在此执行:测试之前的setup")
return func(t *testing.T) {
t.Log("如有需要在此执行:测试之后的teardown")
}
}

//子测试的setup与teardown
func setupSubTest(t *testing.T) func(t *testing.T) {
t.Log("如有需要在此执行:子测试之前的setup")
return func(t *testing.T) {
t.Log("如有需要在此执行:子测试之后的teardown")
}
}

使用工具函数setUp和tearDown

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func TestSplit6(t *testing.T) {
//定义test结构体
type test struct {
input string
sep string
want []string
}

//测试用例使用map储存
tests := map[string]test{
"case_1": {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
"case_2": {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
"case_3": {input: "abcd", sep: "bc", want: []string{"a", "d"}},
"case_4": {input: "上海自来上来自海上", sep: "上", want: []string{"", "海自来", "来自海", ""}},
}
//测试之前执行setUp操作
teardown := setupTestCase(t)
//测试之后执行tearDown操作
defer teardown(t)

for name, tc := range tests {
//使t.Run()执行子测试
t.Run(name, func(t *testing.T) {
//子测试之前执行setUp操作
teardown := setupSubTest(t)
//子测试之后执行teardown操作
defer teardown(t)
got := Split(tc.input, tc.sep)
if !reflect.DeepEqual(tc.want, got) {
t.Fatalf("want %#v but got %#v\n", tc.want, got)
}
})
}
}

测试结果输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
05split>go test -run="TestS
plit6" -v
write setUp code here...
=== RUN TestSplit6
TestSplit6: split_setUp_TearDown_sub_test.go:10: 如有需要在此执行:测试之前的setup
=== RUN TestSplit6/case_3
TestSplit6/case_3: split_setUp_TearDown_sub_test.go:18: 如有需要在此执行:子测试之前的setup
TestSplit6/case_3: split_setUp_TearDown_sub_test.go:20: 如有需要在此执行:子测试之后的teardown
=== RUN TestSplit6/case_4
TestSplit6/case_4: split_setUp_TearDown_sub_test.go:18: 如有需要在此执行:子测试之前的setup
TestSplit6/case_4: split_setUp_TearDown_sub_test.go:20: 如有需要在此执行:子测试之后的teardown
=== RUN TestSplit6/case_1
TestSplit6/case_1: split_setUp_TearDown_sub_test.go:18: 如有需要在此执行:子测试之前的setup
TestSplit6/case_1: split_setUp_TearDown_sub_test.go:20: 如有需要在此执行:子测试之后的teardown
=== RUN TestSplit6/case_2
TestSplit6/case_2: split_setUp_TearDown_sub_test.go:18: 如有需要在此执行:子测试之前的setup
TestSplit6/case_2: split_setUp_TearDown_sub_test.go:20: 如有需要在此执行:子测试之后的teardown
TestSplit6: split_setUp_TearDown_sub_test.go:12: 如有需要在此执行:测试之后的teardown
--- PASS: TestSplit6 (0.00s)
--- PASS: TestSplit6/case_3 (0.00s)
--- PASS: TestSplit6/case_4 (0.00s)
--- PASS: TestSplit6/case_1 (0.00s)
--- PASS: TestSplit6/case_2 (0.00s)
PASS
write teardown code here...
ok github.com/DurianLollipop/StudyGo/day08/05split 0.119s

示例函数

示例函数的格式

go test特殊对待的第三种函数就是示例函数,他们的函数名以Example为前缀。他们既没有参数,也没有返回值。

1
func Example(){// ...}

示例函数示例

1
2
3
4
5
func ExampleSplit() {
fmt.Println(Split("a:b:c", ":"))
//Output:
//[a b c]
}

编写示例函数又如下三个好处:

  • 1、示例函数能够作为文档直接使用,例如基于Web的godoc中能把示例函数与对应的函数或包关联。

  • 2、示例函数只要包含了//Output:也是可以通过go test运行的可执行测试。

    1
    2
    3
    4
    5
    05split>go test -run=ExampleSplit
    write setUp code here...
    PASS
    write teardown code here...
    ok github.com/DurianLollipop/StudyGo/day08/05split 0.222s
  • 3、示例函数提供了可以直接运行的示例代码,可以直接在golanggodoc文档服务器使用Go Playground运行示例代码。