go示例测试
go 示例测试
testing 包除了测试,还提供了运行并验证示例的功能。示例,一方面是文档的效果,是关于某个功能的使用例子;另一方面,可以被当做测试运行。
一个示例的例子如下:
1 | func ExampleHello() { |
如果 Output: Hello
改为:Output: hello
,运行测试会失败,提示:
1 | got: |
一个示例函数以 Example 开头,如果示例函数包含以 “Output” 开头的行注释,在运行测试时,go 会将示例函数的输出和 “Output” 注释中的值做比较,就如上面的例子。
有时候,输出顺序可能不确定,比如循环输出 map 的值,那么可以使用 “Unordered output” 开头的注释。
如果示例函数没有 “Output” 注释,该示例函数只会被编译而不会被运行。
命名约定
Go 语言通过大量的命名约定来简化工具的复杂度,规范代码的风格。对示例函数的命名有如下约定:
- 包级别的示例函数,直接命名为
func Example() { ... }
- 函数 F 的示例,命名为
func ExampleF() { ... }
- 类型 T 的示例,命名为
func ExampleT() { ... }
- 类型 T 上的 方法 M 的示例,命名为
func ExampleT_M() { ... }
有时,我们想要给 包/类型/函数/方法 提供多个示例,可以通过在示例函数名称后附加一个不同的后缀来实现,但这种后缀必须以小写字母开头,如:
1 | func Example_suffix() { ... } |
通常,示例代码会放在单独的示例文件中,命名为 example_test.go
。可以查看 io
包中的 example_test.go
了解示例的编写。
实现原理
本节开头提到了示例的两个作用,它们分别是由 godoc
和 go test
这两个命令实现的。
在执行 go test
时,会运行示例。具体的实现原理,可以通过阅读 go test
命令源码和 testing 包中 example.go
文件了解。
源代码文件
源代码文件example.go
中包含SayHello()
、SayGoodbye()
和PrintNames()
三个方法,如下所示:
1 | package gotest |
这几个方法打印内容略有不同,分别代表一种典型的场景:
- SayHello():只有一行打印输出
- SayGoodbye():有两行打印输出
- PrintNames():有多行打印输出,且由于Map数据结构的原因,多行打印次序是随机的。
测试文件
测试文件example_test.go
中包含3个测试方法,于源代码文件中的3个方法一一对应,测试文件如下所示:
1 | package gotest_test |
例子测试函数命名规则为”Examplexxx”,其中”xxx”为自定义的标识,通常为待测函数名称。
这三个测试函数分别代表三种场景:
- ExampleSayHello(): 待测试函数只有一行输出,使用”// OutPut: “检测。
- ExampleSayGoodbye():待测试函数有多行输出,使用”// OutPut: “检测,其中期望值也是多行。
- ExamplePrintNames():待测试函数有多行输出,但输出次序不确定,使用”// Unordered output:”检测。
注:字符串比较时会忽略前后的空白字符。
执行测试
命令行下,使用go test
或go test example_test.go
命令即可启动测试,如下所示:
1 | E:\OpenSource\GitHub\RainbowMango\GoExpertProgrammingSourceCode\GoExpert\src\gotest>go test example_test.go |
总结
- 例子测试函数名需要以”Example”开头;
- 检测单行输出格式为“// Output: <期望字符串>”;
- 检测多行输出格式为“// Output: \ <期望字符串> \ <期望字符串>”,每个期望字符串占一行;
- 检测无序输出格式为”// Unordered output: \ <期望字符串> \ <期望字符串>”,每个期望字符串占一行;
- 测试字符串时会自动忽略字符串前后的空白字符;
- 如果测试函数中没有“Output”标识,则该测试函数不会被执行;
- 执行测试可以使用
go test
,此时该目录下的其他测试文件也会一并执行; - 执行测试可以使用
go test <xxx_test.go>
,此时仅执行特定文件中的测试函数;