Go语言基础之单元测试

1.go test工具

Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。

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

*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。

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

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

测试函数的格式

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

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

测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头,举几个例子:

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

其中参数t用于报告测试失败和附加的日志信息。 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) 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函数,具体实现如下:

// split/split.gopackage splitimport "strings"// split package with a single split function.// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
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的测试文件,并定义一个测试函数如下:

// split/split_test.gopackage splitimport ("reflect""testing"
)func TestSplit(t *testing.T) { // 测试函数名必须以Test开头,必须接收一个*testing.T类型参数got := Split("a:b:c", ":")         // 程序输出的结果want := []string{"a", "b", "c"}    // 期望的结果if !reflect.DeepEqual(want, got) { // 因为slice不能比较直接,借助反射包中的方法比较t.Errorf("expected:%v, got:%v", want, got) // 测试失败输出错误提示}
}

此时split这个包中的文件如下:

split $ ls -l
total 16
-rw-r--r--  1 liwenzhou  staff  408  4 29 15:50 split.go
-rw-r--r--  1 liwenzhou  staff  466  4 29 16:04 split_test.go

split包路径下,执行go test命令,可以看到输出结果如下:

split $ go test
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.005s

一个测试用例有点单薄,我们再编写一个测试使用多个字符切割字符串的例子,在split_test.go中添加如下测试函数:

func TestMoreSplit(t *testing.T) {got := Split("abcd", "bc")want := []string{"a", "d"}if !reflect.DeepEqual(want, got) {t.Errorf("expected:%v, got:%v", want, got)}
}

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

split $ go test
--- FAIL: TestMultiSplit (0.00s)split_test.go:20: expected:[a d], got:[a cd]
FAIL
exit status 1
FAIL    github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

这一次,我们的测试失败了。我们可以为go test命令添加-v参数,查看测试函数名称和运行时间:

split $ go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestMoreSplit
--- FAIL: TestMoreSplit (0.00s)split_test.go:21: expected:[a d], got:[a cd]
FAIL
exit status 1
FAIL    github.com/Q1mi/studygo/code_demo/test_demo/split       0.005s

这一次我们能清楚的看到是TestMoreSplit这个测试没有成功。 还可以在go test命令后添加-run参数,它对应一个正则表达式,只有函数名匹配上的测试函数才会被go test命令执行。

split $ go test -v -run="More"
=== RUN   TestMoreSplit
--- FAIL: TestMoreSplit (0.00s)split_test.go:21: expected:[a d], got:[a cd]
FAIL
exit status 1
FAIL    github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

现在我们回过头来解决我们程序中的问题。很显然我们最初的split函数并没有考虑到sep为多个字符的情况,我们来修复下这个Bug:

package splitimport "strings"// split package with a single split function.// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
func Split(s, sep string) (result []string) {i := strings.Index(s, sep)for i > -1 {result = append(result, s[:i])s = s[i+len(sep):] // 这里使用len(sep)获取sep的长度i = strings.Index(s, sep)}result = append(result, s)return
}

这一次我们再来测试一下,我们的程序。注意,当我们修改了我们的代码之后不要仅仅执行那些失败的测试函数,我们应该完整的运行所有的测试,保证不会因为修改代码而引入了新的问题。

split $ go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestMoreSplit
--- PASS: TestMoreSplit (0.00s)
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

这一次我们的测试都通过了。

测试组

我们现在还想要测试一下split函数对中文字符串的支持,这个时候我们可以再编写一个TestChineseSplit测试函数,但是我们也可以使用如下更友好的一种方式来添加更多的测试用例。

func TestSplit(t *testing.T) {// 定义一个测试用例类型type test struct {input stringsep   stringwant  []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(got, tc.want) {t.Errorf("expected:%v, got:%v", tc.want, got)}}
}

我们通过上面的代码把多个测试用例合到一起,再次执行go test命令。

split $ go test -v
=== RUN   TestSplit
--- FAIL: TestSplit (0.00s)split_test.go:42: expected:[河有 又有河], got:[ 河有 又有河]
FAIL
exit status 1
FAIL    github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

我们的测试出现了问题,仔细看打印的测试失败提示信息:expected:[河有 又有河], got:[ 河有 又有河],你会发现[ 河有 又有河]中有个不明显的空串,这种情况下十分推荐使用%#v的格式化方式。

我们修改下测试用例的格式化输出错误提示部分:

func TestSplit(t *testing.T) {...for _, tc := range tests {got := Split(tc.input, tc.sep)if !reflect.DeepEqual(got, tc.want) {t.Errorf("expected:%#v, got:%#v", tc.want, got)}}
}

此时运行go test命令后就能看到比较明显的提示信息了:

split $ go test -v
=== RUN   TestSplit
--- FAIL: TestSplit (0.00s)split_test.go:42: expected:[]string{"河有", "又有河"}, got:[]string{"", "河有", "又有河"}
FAIL
exit status 1
FAIL    github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

子测试

看起来都挺不错的,但是如果测试用例比较多的时候,我们是没办法一眼看出来具体是哪个测试用例失败了。我们可能会想到下面的解决办法:

func TestSplit(t *testing.T) {type test struct { // 定义test结构体input stringsep   stringwant  []string}tests := map[string]test{ // 测试用例使用map存储"simple":      {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},"wrong sep":   {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},"more sep":    {input: "abcd", sep: "bc", want: []string{"a", "d"}},"leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"河有", "又有河"}},}for name, tc := range tests {got := Split(tc.input, tc.sep)if !reflect.DeepEqual(got, tc.want) {t.Errorf("name:%s expected:%#v, got:%#v", name, tc.want, got) // 将测试用例的name格式化输出}}
}

上面的做法是能够解决问题的。同时Go1.7+中新增了子测试,我们可以按照如下方式使用t.Run执行子测试:

func TestSplit(t *testing.T) {type test struct { // 定义test结构体input stringsep   stringwant  []string}tests := map[string]test{ // 测试用例使用map存储"simple":      {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},"wrong sep":   {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},"more sep":    {input: "abcd", sep: "bc", want: []string{"a", "d"}},"leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"河有", "又有河"}},}for name, tc := range tests {t.Run(name, func(t *testing.T) { // 使用t.Run()执行子测试got := Split(tc.input, tc.sep)if !reflect.DeepEqual(got, tc.want) {t.Errorf("expected:%#v, got:%#v", tc.want, got)}})}
}

此时我们再执行go test命令就能够看到更清晰的输出内容了:

split $ go test -v
=== RUN   TestSplit
=== RUN   TestSplit/leading_sep
=== RUN   TestSplit/simple
=== RUN   TestSplit/wrong_sep
=== RUN   TestSplit/more_sep
--- FAIL: TestSplit (0.00s)--- FAIL: TestSplit/leading_sep (0.00s)split_test.go:83: expected:[]string{"河有", "又有河"}, got:[]string{"", "河有", "又有河"}--- PASS: TestSplit/simple (0.00s)--- PASS: TestSplit/wrong_sep (0.00s)--- PASS: TestSplit/more_sep (0.00s)
FAIL
exit status 1
FAIL    github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

这个时候我们要把测试用例中的错误修改回来:

func TestSplit(t *testing.T) {...tests := map[string]test{ // 测试用例使用map存储"simple":      {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},"wrong sep":   {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},"more sep":    {input: "abcd", sep: "bc", want: []string{"a", "d"}},"leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"", "河有", "又有河"}},}...
}

我们都知道可以通过-run=RegExp来指定运行的测试用例,还可以通过/来指定要运行的子测试用例,例如:go test -v -run=Split/simple只会运行simple对应的子测试用例。

测试覆盖率

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

Go提供内置功能来检查你的代码覆盖率。我们可以使用go test -cover来查看测试覆盖率。例如:

split $ go test -cover
PASS
coverage: 100.0% of statements
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.005s

从上面的结果可以看到我们的测试用例覆盖了100%的代码。

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

split $ go test -cover -coverprofile=c.out
PASS
coverage: 100.0% of statements
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.005s

上面的命令会将覆盖率相关的信息输出到当前文件夹下面的c.out文件中,然后我们执行go tool cover -html=c.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。

Go test cover

上图中每个用绿色标记的语句块表示被覆盖了,而红色的表示没有被覆盖。

2.基准测试

基准测试函数格式

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

func BenchmarkName(b *testing.B){// ...
}

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

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函数编写基准测试如下:

func BenchmarkSplit(b *testing.B) {for i := 0; i < b.N; i++ {Split("沙河有沙又有河", "沙")}
}

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

split $ go test -bench=Split
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/split
BenchmarkSplit-8        10000000               203 ns/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       2.255s

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

我们还可以为基准测试添加-benchmem参数,来获得内存分配的统计数据。

split $ go test -bench=Split -benchmem
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/split
BenchmarkSplit-8        10000000               215 ns/op             112 B/op          3 allocs/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       2.394s

其中,112 B/op表示每次操作内存分配了112字节,3 allocs/op则表示每次操作进行了3次内存分配。 我们将我们的Split函数优化如下:

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])s = s[i+len(sep):] // 这里使用len(sep)获取sep的长度i = strings.Index(s, sep)}result = append(result, s)return
}

这一次我们提前使用make函数将result初始化为一个容量足够大的切片,而不再像之前一样通过调用append函数来追加。我们来看一下这个改进会带来多大的性能提升:

split $ go test -bench=Split -benchmem
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/split
BenchmarkSplit-8        10000000               127 ns/op              48 B/op          1 allocs/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       1.423s

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

性能比较函数

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

性能比较函数通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用。举个例子如下:

func benchmark(b *testing.B, size int){/* ... */}
func Benchmark10(b *testing.B){ benchmark(b, 10) }
func Benchmark100(b *testing.B){ benchmark(b, 100) }
func Benchmark1000(b *testing.B){ benchmark(b, 1000) }

例如我们编写了一个计算斐波那契数列的函数如下:

// fib.go// Fib 是一个计算第n个斐波那契数的函数
func Fib(n int) int {if n < 2 {return n}return Fib(n-1) + Fib(n-2)
}

我们编写的性能比较函数如下:

// fib_test.gofunc benchmarkFib(b *testing.B, n int) {for i := 0; i < b.N; i++ {Fib(n)}
}func BenchmarkFib1(b *testing.B)  { benchmarkFib(b, 1) }
func BenchmarkFib2(b *testing.B)  { benchmarkFib(b, 2) }
func BenchmarkFib3(b *testing.B)  { benchmarkFib(b, 3) }
func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10) }
func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) }
func BenchmarkFib40(b *testing.B) { benchmarkFib(b, 40) }

运行基准测试:

split $ go test -bench=.
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/fib
BenchmarkFib1-8         1000000000               2.03 ns/op
BenchmarkFib2-8         300000000                5.39 ns/op
BenchmarkFib3-8         200000000                9.71 ns/op
BenchmarkFib10-8         5000000               325 ns/op
BenchmarkFib20-8           30000             42460 ns/op
BenchmarkFib40-8               2         638524980 ns/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/fib 12.944s

这里需要注意的是,默认情况下,每个基准测试至少运行1秒。如果在Benchmark函数返回时没有到1秒,则b.N的值会按1,2,5,10,20,50,…增加,并且函数再次运行。

最终的BenchmarkFib40只运行了两次,每次运行的平均值只有不到一秒。像这种情况下我们应该可以使用-benchtime标志增加最小基准时间,以产生更准确的结果。例如:

split $ go test -bench=Fib40 -benchtime=20s
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/fib
BenchmarkFib40-8              50         663205114 ns/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/fib 33.849s

这一次BenchmarkFib40函数运行了50次,结果就会更准确一些了。

使用性能比较函数做测试的时候一个容易犯的错误就是把b.N作为输入的大小,例如以下两个例子都是错误的示范:

// 错误示范1
func BenchmarkFibWrong(b *testing.B) {for n := 0; n < b.N; n++ {Fib(n)}
}// 错误示范2
func BenchmarkFibWrong2(b *testing.B) {Fib(b.N)
}

重置时间

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

func BenchmarkSplit(b *testing.B) {time.Sleep(5 * time.Second) // 假设需要做一些耗时的无关操作b.ResetTimer()              // 重置计时器for i := 0; i < b.N; i++ {Split("沙河有沙又有河", "沙")}
}

并行测试

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

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

func BenchmarkSplitParallel(b *testing.B) {// b.SetParallelism(1) // 设置使用的CPU数b.RunParallel(func(pb *testing.PB) {for pb.Next() {Split("沙河有沙又有河", "沙")}})
}

执行一下基准测试:

split $ go test -bench=.
goos: darwin
goarch: amd64
pkg: github.com/Q1mi/studygo/code_demo/test_demo/split
BenchmarkSplit-8                10000000               131 ns/op
BenchmarkSplitParallel-8        50000000                36.1 ns/op
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       3.308s

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

3.Setup与TearDown

测试程序有时需要在测试之前进行额外的设置(setup)或在测试之后进行拆卸(teardown)。

TestMain

通过在*_test.go文件中定义TestMain函数来可以在测试之前进行额外的设置(setup)或在测试之后进行拆卸(teardown)操作。

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

一个使用TestMain来设置Setup和TearDown的示例如下:

func TestMain(m *testing.M) {fmt.Println("write setup code here...") // 测试之前的做一些设置// 如果 TestMain 使用了 flags,这里应该加上flag.Parse()retCode := m.Run()                         // 执行测试fmt.Println("write teardown code here...") // 测试之后做一些拆卸工作os.Exit(retCode)                           // 退出测试
}

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

子测试的Setup与Teardown

有时候我们可能需要为每个测试集设置Setup与Teardown,也有可能需要为每个子测试设置Setup与Teardown。下面我们定义两个函数工具函数如下:

// 测试集的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")}
}

使用方式如下:

func TestSplit(t *testing.T) {type test struct { // 定义test结构体input stringsep   stringwant  []string}tests := map[string]test{ // 测试用例使用map存储"simple":      {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},"wrong sep":   {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},"more sep":    {input: "abcd", sep: "bc", want: []string{"a", "d"}},"leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"", "河有", "又有河"}},}teardownTestCase := setupTestCase(t) // 测试之前执行setup操作defer teardownTestCase(t)            // 测试之后执行testdoen操作for name, tc := range tests {t.Run(name, func(t *testing.T) { // 使用t.Run()执行子测试teardownSubTest := setupSubTest(t) // 子测试之前执行setup操作defer teardownSubTest(t)           // 测试之后执行testdoen操作got := Split(tc.input, tc.sep)if !reflect.DeepEqual(got, tc.want) {t.Errorf("expected:%#v, got:%#v", tc.want, got)}})}
}

测试结果如下:

split $ go test -v
=== RUN   TestSplit
=== RUN   TestSplit/simple
=== RUN   TestSplit/wrong_sep
=== RUN   TestSplit/more_sep
=== RUN   TestSplit/leading_sep
--- PASS: TestSplit (0.00s)split_test.go:71: 如有需要在此执行:测试之前的setup--- PASS: TestSplit/simple (0.00s)split_test.go:79: 如有需要在此执行:子测试之前的setupsplit_test.go:81: 如有需要在此执行:子测试之后的teardown--- PASS: TestSplit/wrong_sep (0.00s)split_test.go:79: 如有需要在此执行:子测试之前的setupsplit_test.go:81: 如有需要在此执行:子测试之后的teardown--- PASS: TestSplit/more_sep (0.00s)split_test.go:79: 如有需要在此执行:子测试之前的setupsplit_test.go:81: 如有需要在此执行:子测试之后的teardown--- PASS: TestSplit/leading_sep (0.00s)split_test.go:79: 如有需要在此执行:子测试之前的setupsplit_test.go:81: 如有需要在此执行:子测试之后的teardownsplit_test.go:73: 如有需要在此执行:测试之后的teardown
=== RUN   ExampleSplit
--- PASS: ExampleSplit (0.00s)
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

4.示例函数

示例函数的格式

go test特殊对待的第三种函数就是示例函数,它们的函数名以Example为前缀。它们既没有参数也没有返回值。标准格式如下:

func ExampleName() {// ...
}

示例函数示例

下面的代码是我们为Split函数编写的一个示例函数:

func ExampleSplit() {fmt.Println(split.Split("a:b:c", ":"))fmt.Println(split.Split("沙河有沙又有河", "沙"))// Output:// [a b c]// [ 河有 又有河]
}

为你的代码编写示例代码有如下三个用处:

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

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

split $ go test -run Example
PASS
ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.006s

示例函数提供了可以直接运行的示例代码,可以直接在golang.orggodoc文档服务器上使用Go Playground运行示例代码。下图为strings.ToUpper函数在Playground的示例函数效果。

package main
import (
"fmt"
"strings")
func main() {
fmt.Println(strings.ToUpper("Go Upper"))
}

参考文章:

https://www.fansimao.com/1006331.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/249256.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maven的Docker镜像二次打包,再次推送至Harbor中

之所以如此操作&#xff0c;主要原因是&#xff0c;官版的镜像中默认的setting.xml已内置好&#xff0c;不容易修改&#xff0c; 重新二次打包&#xff0c;可以指定我们自己的setting.xml配置&#xff0c;配置自己的私服地址以及解决默认Maven仓库国内下载速度慢的问题 一、创…

ssl数字证书是什么

SSL证书是一种数字证书&#xff0c;用于在网络传输中提供加密和身份验证功能&#xff0c;从而保护数据的安全性和完整性。正规的SSL证书大多是由由权威的证书颁发机构&#xff08;CA&#xff09;颁发的&#xff0c;例如Certum、Digicert、Sectigo等&#xff0c;它们颁发的SSL数…

[机器学习]简单线性回归——最小二乘法

一.线性回归及最小二乘法概念 2.代码实现 # 0.引入依赖 import numpy as np import matplotlib.pyplot as plt# 1.导入数据 points np.genfromtxt(data.csv, delimiter,) # points[0,0]# 提取points中的两列数据&#xff0c;分别作为x&#xff0c;y x points[:, 0] y poi…

【Linux】初始进程地址空间

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 目录 一、再谈fork二、程序地址空间2.1代码验证 三、虚拟地址&am…

Https加密超文本传输协议的运用

一、https的相关知识 1.1 https的简介 HTTPS &#xff08;全称&#xff1a;Hypertext Transfer Protocol Secure &#xff09;&#xff0c;是以安全为目标的 HTTP 通道&#xff0c;在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 。HTTPS 在HTTP 的基础下加…

ElementUI Form:Checkbox 多选框

ElementUI安装与使用指南 Checkbox 多选框 点击下载learnelementuispringboot项目源码 效果图 el-checkbox.vue &#xff08;Checkbox 多选框&#xff09;页面效果图 项目里el-checkbox.vue代码 <script> const cityOptions [上海, 北京, 广州, 深圳] export def…

C语言操作符

文章目录 1:算术操作符2:移位操作符(移动的是二进制序列中的补码)2.1:知识补充(原码,反码,补码与二进制)2.2:左移操作符(<<)2.2:右移操作符(>>)2.2.1:逻辑右移2.2.2:算术右移 3:位操作符(运算用的是二进制位的补码)3.1:按位与操作符(&)3.2:按位或操作符(|)3.3:…

系统分析师-22年-下午题目

系统分析师-22年-下午题目 更多软考知识请访问 https://ruankao.blog.csdn.net/ 试题一必答&#xff0c;二、三、四、五题中任选其中两题作答 试题一 (25分) 说明 某软件公司拟开发一套博客系统&#xff0c;要求能够向用户提供一个便捷发布自已心得&#xff0c;及时有效的…

使用 axios 请求库,设置请求拦截

什么是 axios&#xff1f; 基于promise网络请求库&#xff0c;可以同构&#xff08;同一套代码可以运行在浏览器&#xff09;&#xff0c;在服务端&#xff0c;使用原生node.js的http模块&#xff0c;在客户端&#xff08;浏览器&#xff09;中&#xff0c;使用XMLHttpRequests…

Kotlin快速入门系列2

Kotlin的基本数据类型 Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于 Java 的是&#xff0c;字符不属于数值类型&#xff0c;是一个独立的数据类型。 Java和kotlin数据类型对照如下&#xff1a; Java基本数据类型 Kotlin对象数据类型 数据类…

【Linux】基本指令(上)

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:Linux ⚙️操作环境:Xshell (操作系统:CentOS 7.9 64位) 目录 Xshell快捷键 Linux基本指令 ls指令 pwd指令 cd指令 touch指令 mkdir指令 rmdir指令/rm指令 结语 Xshell快捷键 AltEnter 全屏/取消全屏 Tab 进…

HarmonyOS模拟器启动失败,电脑蓝屏解决办法

1、在Tool->Device Manager管理界面中&#xff0c;通过Wipe User Data清理模拟器用户数据&#xff0c;然后重启模拟器&#xff1b;如果该方法无效&#xff0c;需要Delete删除已创建的Local Emulater。 2、在Tool->SDK Manager管理界面的PlatForm选项卡中&#xff0c;取消…

开阳630hv100的代码编译以及软件制作步骤

打开项目功能步骤&#xff1a; 编译awtk功能&#xff1a; 选中awtk工程&#xff0c;先编译一次awtk sdk&#xff08;如下图的3和4步骤&#xff09;&#xff1b; 编译项目代码&#xff08;如下图步骤5和6&#xff09;&#xff1b; 编译完成后&#xff0c;软件路径&#xff1a;…

1.理解AOP,使用AOP

目录 1AOP基础 1.1 AOP概述 1.2AOP快速使用 2.3 AOP核心概念 1AOP基础 首先介绍一下什么是AOP&#xff0c;再通过一个快速入门程序&#xff0c;让大家快速体验AOP程序的开发。最后再介绍AOP当中所涉及到的一些核心的概念。 1.1 AOP概述 什么是AOP&#xff1f; 说白了&am…

FPGA高端项目:Xilinx Artix7系列FPGA 多路视频缩放拼接 工程解决方案 提供4套工程源码+技术支持

目录 1、前言版本更新说明给读者的一封信FPGA就业高端项目培训计划免责声明 2、相关方案推荐我这里已有的FPGA图像缩放方案我已有的FPGA视频拼接叠加融合方案本方案的Xilinx Kintex7系列FPGA上的ov5640版本本方案的Xilinx Kintex7系列FPGA上的HDMI版本 3、设计思路框架设计框图…

代理模式(静态代理、JDK 动态代理、CGLIB 动态代理)

代理模式(静态代理、JDK 动态代理、CGLIB 动态代理) 一、代理模式概述1. 生活中的代理案例2. 为什么要使用代理3. 代理模式在 Java 中的应用4. 概述5. 生活中代理图示二、代理的实现方式1. Java 中代理图示2. 静态代理2.1 案例2.2 实现案例2.3 静态代理存在的问题三、动态代理…

Vmware 无法开启虚拟化解决方法

最近遇到了Vmware无法开启虚拟化的问题,已经解决,记录一下解决经过。 我遇到的情况是BIOS已经开启虚拟化,HV服务也停用了,但是Vmware仍然提示模块“VPMC”启动失败。网上的解决方案千篇一律,基本都是排查BIOS、停用Windows的虚拟化功能、停用HV主机服务、Vmware配置中关闭…

RK3588平台开发系列讲解(视频篇)RKMedia的VDEC模块

文章目录 一、 VDEC模块支持的编码标准介绍二、VDEC API的调用三、VDEC解码流程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢RKMedia是RK提供的一种多媒体处理方案,可实现音视频捕获、音视频输出、音视频编解码等功能。 一、 VDEC模块支持的编码标准介绍 RK3688 V…

【51单片机】点亮第一个LED灯

目录 点亮第一个LED灯单片机 GPIO 介绍GPIO 概念GPIO 结构 LED简介软件设计点亮D1指示灯LED流水灯 橙色 点亮第一个LED灯 单片机 GPIO 介绍 GPIO 概念 GPIO&#xff08;general purpose intput output&#xff09; 是通用输入输出端口的简称&#xff0c; 可以通过软件来控制…

使用宝塔面板访问MySQL数据库

文章目录 前言一、安装访问工具二、查看数据库总结 前言 前面我们已经部署了前后端项目&#xff0c;但是却不能得到数据库的信息&#xff0c;看有谁再使用你的项目。例如员工、用户等等。本次博客进行讲解如何在宝塔面板里面访问MySQL数据库。 一、安装访问工具 1、打开软件商…