之前文章有介绍过 go test coverage 单测覆盖率 和Go test基础用法,今天这里主要介绍 go 单测中比较特殊的一种场景:包外测试。初次看到这个名字,我还以为就是单独创建一个新目录,所有的单测用例统一都汇总到这个目录下,但其实并不是。
go语言限定在一个目录下只能声明一个 package,如果尝试在一个目录下声明多个 package,编译器会有报错提示:Multiple packages in the directory:
。但这种情况也是例外的,包外测试就是一个例外。
go语言如何存放单测文件呢,最通用的方式就是在当前目录下创建*_test.go 文件。形如下面这样,但 main_test.go 和 main.go 声明的 package 是相同的。
结合图示,go 包外测试就是 main_test.go 中声明的 package 不再是 main,而是 main_test。这样就出现了同一个目录下,同时存在 package 为 main 和 mian_test 的情况,这是被编译器支持的。
现在包外测试已经搞明白了:同一个目录下,比如声明的 package 为 ol,还可以声明 ol_test 的包。但也有前提限制,声明 package ol_test 的 go 文件需要是单测文件(文件后缀是_test.go)。
问题来了,包外测试用来解决什么问题呢,我怎么就没有遇到过包外测试的情况?
虽然 ol 和 ol_test 在同一个目录下,但因为它俩的 package 不相同,两个包的就是独立隔离的,ol_test 中的单测用例就不能访问 ol 中的私有方法了。
外包测试模式对应了黑盒测试,包内测试对应了白盒测试。从黑盒和白盒的角度来看,包外测试和单独创建一个包目录,然后统一将单测文件集中到这个目录下,没有什么区别。包外测试真正重要的地方是:解决循环依赖问题。
我们思考一个问题,go 里面单测使用的包是 testing
,单测需要引入这个包来执行。testing 包内部会有很多处理逻辑,也需要依赖其他底层的包,比如说 strings、strconv、fmt 等等。问题来了,如果 strings 包想要写单测文件该怎么办呢?testing 内部依赖了 strings 包,现在 strings 包反过来又要依赖 testing 包,这就形成了循环依赖问题。
我们看看 strings 包的单测文件,它内部的单测使用的就是包外测试。在同一个目录下,也只能使用包外测试来规避这种情况。
我们看 testing 包的单测用例,也是用了包外测试的模式。这么想来,我们业务代码中最好不要使用这种模式,在设计代码结构结构时,包的调用层级我们就应该设计好,避免出现循环调用的情况。
现在通过包外测试将代码做了隔离,包外测试就访问不到包内的私有方法和变量了。神奇的是,go没有彻底把这个路给封死,我们可以通过包内测试来把他们暴露出来。
举个例子,我声明一个包 order:order.go 文件中声明了一个私有变量 number,order_test 是包外测试,声明的包名是 order_test。无论如何,order_test 是无法访问这个是有变量的。
所以,我们声明了一个 order 包内的变量导出单测文件,order_export_test,已这个单测文件为沟通桥梁,将私有变量变得可导出。
最后在包外测试中,引用 order 包,直接使用这个导出的变量就可以。通过这里例子我们发现,同一个包下,包外测试可以通过包内测试做中转,来访问包内的私有成员。但这也仅限于同一个报下,跨包的单测导出是不能被访问的。