天天看点

Golang如何写能进入文档的测试先看看效果吧可测试的Example结语

先看看效果吧

当不小心点开网页文档的时候,是不是有看到过这样的展示示例代码的tab?

Golang如何写能进入文档的测试先看看效果吧可测试的Example结语

或者在IDE里看很多函数的简介时是不是有这样的:

Golang如何写能进入文档的测试先看看效果吧可测试的Example结语

有没心生敬畏,觉得很神奇?

其实这就是Golang的testing提供的一种能力,把单测代码同时作为文档,我们来看看要怎么实现吧。

可测试的Example

Example测试会被展示进package的文档,并可以如单测一样被验证。可以在godoc展示的web网页中特别展示出来。

Example本身也是测试,它会和正常的测试一起编译和执行,和它们一样放在xxx_test.go的文件中,但是它的函数签名不一样。

函数签名

Example的函数签名以Example开头,后面紧跟被示例的函数的名字,并且没有入参:

func ExampleFuncName() {
	……
}
           

比如我现在实现一个函数 AdjacentMixString:

util.go

……
// 交互地混合两个字符串
func AdjacentMixString(str1, str2 string) string {
	builder := strings.Builder{}
	builder.Grow(len(str1) + len(str2))
	i := 0
	for ; i < len(str1) && i < len(str2); i++ {
		builder.WriteByte(str1[i])
		builder.WriteByte(str2[i])
	}
	if len(str1) > i {
		builder.WriteString(str1[i:])
	}
	if len(str2) > i {
		builder.WriteString(str2[i:])
	}
	return builder.String()
}
           

那它的示例就应该这么写:

func ExampleAdjacentMixString() {
	……
}
           

执行测试

和普通的测试一样,你可以通过go test指令以及相关的option来运行Example测试。

我们来随便写个Example到util_test.go文件中:

……
func ExampleAdjacentMixString() {
	// should output 1a2b3c
	AdjacentMixString("123", "abc")
}
           

已经可以看到效果了:

Golang如何写能进入文档的测试先看看效果吧可测试的Example结语

然后来运行它:

admin@……/util$ go test -v -run=AdjacentMixString
=== RUN   ExampleAdjacentMixString
--- PASS: ExampleAdjacentMixString (0.00s)
PASS
ok      ……/util    1.229s
           

执行通过了,但好像啥也没测。

目前为止这个Example还不具备“测试”的作用,仅仅是一个能编译通过的“文档”。

Output注释

Example测试不提供Fail等方法,但是可以通过标准输出来实现简单的断言。

在执行Example测试时,测试框架会截获标准输出,并与"output:"后的注释进行比较。如果相等的话,就能通过测试。

这样,我们改造我们的Example。

func ExampleAdjacentMixString() {
	fmt.Println(AdjacentMixString("", "abc"))
	fmt.Println(AdjacentMixString("123", ""))
	fmt.Println(AdjacentMixString("1", "abc"))
	fmt.Println(AdjacentMixString("123", "a"))
	fmt.Println(AdjacentMixString("123", "abcd"))
	fmt.Println(AdjacentMixString("1234", "abc"))
	// output:
	// abc
	// 123
	// 1abc
	// 1a23
	// 1a2b3cd
	// 1a2b3c4
}
           

再次运行。又通过了。和上面的输出几乎一致就不贴了。

我们故意改错一点,把output后的abc改成a看看是什么样的:

admin@……/util ‹release*›$ go test -v -run=AdjacentMixString
=== RUN   ExampleAdjacentMixString
--- FAIL: ExampleAdjacentMixString (0.00s)
got:
abc
123
1abc
1a23
1a2b3cd
1a2b3c4
want:
a
123
1abc
1a23
1a2b3cd
1a2b3c4
FAIL
exit status 1
FAIL    ……/util    1.316s
           

可以看到,这个Example成功发挥了一个“测试”的功能。

而文档也变成了这个样子:

Golang如何写能进入文档的测试先看看效果吧可测试的Example结语
Golang如何写能进入文档的测试先看看效果吧可测试的Example结语

Example的命名约定

Godoc使用特定的命名约定来关联Example与其对应的被示例对象。

func ExampleFoo() // 关联到Foo函数或类型
func ExampleA_Go() // 关联到A类型的Go方法
func Example() // 关联整个package

           

还可以通过下划线加小写字母的后缀,给同一个对象指定多个Example。比如可以这么搞:

func ExampleAdjacentMixString() {
	fmt.Println(AdjacentMixString("123", "abcd"))
	// output:
	// 1a2b3cd
}

func ExampleAdjacentMixString_thisIsB() {
	fmt.Println(AdjacentMixString("123", ""))
	// output:
	// 123
}

func ExampleAdjacentMixString_thisIsC() {
	fmt.Println(AdjacentMixString("", "abc"))
	// output:
	// abc
}
           

这样文档中的效果就是:

Golang如何写能进入文档的测试先看看效果吧可测试的Example结语
Golang如何写能进入文档的测试先看看效果吧可测试的Example结语

整个文件作为Example

有的时候一个Example函数不足以表达整个模块的功能,这个时候可以使用整个文件作为Example。比如我给util package做一个Example文件:

util_test.go

package util

import "fmt"

type A struct {
	hi string
}

func (a *A) Go() {
	fmt.Println("This is a file Example")
}

func Example() {
	(&A{}).Go()

	// Output:
	// This is a file Example
}
           

这样,我就可以在godoc上看到整个文件了:

Golang如何写能进入文档的测试先看看效果吧可测试的Example结语

同样的,你也可以建多个文件,每个文件中的Example函数用不同的下划线后缀,来为同一个package建多个示例。

unordered output

最后,如果你的输出是无法确定顺序的,可以使用unordered output来代替output:

func ExampleUnorderedOuput() {
	m := map[string]string{
		"a": "b",
		"c": "d",
		"e": "f",
	}
	for k, v := range m {
		fmt.Println(k, v)
	}
	// unordered output:
	// a b
	// c d
	// e f
}
           

结语

今天我们学习了golang的一个很有意思的特性,Example测试。又涨了点姿势对吧哈哈哈。