go 语言 外部测试包
外部测试包
在测试代码的时候,会遇到包循环依赖导入的问题,Go语言规范是禁止包的循环依赖,为了解决这个问题,可以引入外部包。
外部包使用package xxx_test
方式来命名,比如package bytes_test
就是src/btyes/buffer
外部包的命名方式,_test
后缀告诉go test工具它应该建立一个额外的包来运行测试。
package xxx_test
中的xxx_test
并不需要真的创建这样一个目录,而是在xxx
包下的测试文件中引入这个包名就可以了。btyes
包的源码目录结构如下
/src/bytes
├── boundary_test.go
├── buffer.go
├── buffer_test.go
├── bytes.go
├── bytes_test.go
├── compare_test.go
├── example_test.go
├── export_test.go
├── reader.go
└── reader_test.go
通过外部测试包的方式可以解决导入包循环依赖的问题,因为外部测试包是一个独立的包,所以能够导入那些依赖待测代码本身
的其他辅助包;包内的测试代码就无法做到这点。注意:外部包不能被其它包导入
// 文件 src/btyes/buffer_test.go
package bytes_test // 'btyes目录下并不存在 bytes_test 目录'
import (
. "bytes"
... 代码片段
)
func TestNewBuffer(t *testing.T) {
buf := NewBuffer(testBytes)
check(t, "NewBuffer", buf, testString)
}
... 代码片段
func empty(t *testing.T, testname string, buf *Buffer, s string, fub []byte) {}
go list 命令
通过
GoFiles
查看bytes
包的Go源文件go list -f={{.GoFiles}} bytes // => [buffer.go bytes.go reader.go]
通过
TestGoFiles
查看bytes
包内部的测试代码go list -f={{.TestGoFiles}} bytes // => [export_test.go]
通过
XTestGoFiles
查看bytes
包的外部测试代码,也就是bytes_test包go list -f={{.XTestGoFiles}} bytes // => [buffer_test.go bytes_test.go compare_test.go example_test.go reader_test.go]
访问内部代码
如果在测试中,需要对包内部的没有导出的函数进行测试,可以利用包内的 _test.go
文件,如 export_test.go
,在这个文件中将包的内部函数、方法导出,以供外部测试包使用。
indexBytePortable
方法在 src/bytes/bytes.go 中定义
// src/bytes/bytes.go
func indexBytePortable(s []byte, c byte) int {/* ... */} // 这是一个内部方法
把bytes
包中的内部方法导出,供外部包package bytes_test
使用。因为只有在测试时,才会把内部代码导出,所以导出内部代码是安全的
// src/bytes/export_test.go
package bytes
// Export func for testing
var IndexBytePortable = indexBytePortable // 赋值给包级可导出变量 IndexBytePortable
外部包使用导出的方法IndexBytePortable
// src/bytes/bytes_test.go
package bytes_test
func TestIndexByte(t *testing.T) {
for _, tt := range indexTests {
... 代码片段
posp := IndexBytePortable(a, b) // 导出的内部方法在这里被使用
if posp != tt.i {
t.Errorf(`indexBytePortable(%q, '%c') = %v`, tt.a, b, posp)
}
}
}
测试文件export_test.go
并没有定义测试代码,它只是通过bytes.IndexBytePortable
简单导出了内部的indexBytePortable
函数,这个技巧可以广泛用于位于外部测试包的白盒测试
示例:包循环依赖
目录结构
gott
├── go.mod
├── hi
│ └── hi.go
├── pprint
│ ├── pprint.go
│ └── pprint_test.go
hi.go 文件,引用了 pprint 包
package hi
import (
"fmt"
"gott/pprint"
)
func Say() {
pprint := pprint.PPrint()
fmt.Println(pprint)
}
pprint.go 文件
package pprint
func PPrint() string {
return "I'm PPrint()"
}
pprint_test.go 测试文件,在 pprint 包中引入了 hi 包,而 hi 包中已经引入了 pprint 包,这就导致了 包循环依赖
package pprint
import (
"gott/hi"
"testing"
)
func TestPPrint(t *testing.T) {
PPrint()
hi.Say()
t.Log("expect call PPrint")
}
执行测试,提示 import cycle not allowed in test
go test -v gott/pprint
# gott/pprint
package gott/pprint (test)
imports gott/hi
imports gott/pprint: import cycle not allowed in test
FAIL gott/pprint [setup failed]
FAIL
修改 pprint_test.go 测试文件,使用外部测试包 pprint_test
package pprint_test
import (
"gott/hi"
// 导入 要进行测试的 pprint 包本身
"gott/pprint"
"testing"
)
func TestPPrint(t *testing.T) {
pprint.PPrint()
hi.Say()
t.Log("expect call PPrint")
}
"gott/pprint"
:导入 要进行测试的 pprint 包本身,没有引入外部包时,不需要导入被测试的包本身
执行测试,测试通过
go test -v gott/pprint
=== RUN TestPPrint
I'm PPrint()
pprint_test.go:12: expect call PPrint
--- PASS: TestPPrint (0.00s)
PASS
ok gott/pprint (cached
使用 Go 官方的代码风格:pprint_test.go 文件,因为pprint_test在 pprint 目录下,通过在 import 时,使用 .
选项,可以直接调用PPrint()方法,这使得调用包内的方法和内部测试包一致,整体代码风格得到统一
package pprint_test
import (
"gott/hi"
. "gott/pprint"
"testing"
)
func TestPPrint(t *testing.T) {
PPrint()
hi.Say()
t.Log("expect call PPrint")
}