在 Go 语言中, 导出的标识符是以大写字母开头的,小写字母开头的标识符只可以在它所在的包中使用。我们在开发过程中,仅仅应该导出真正需要的标识符给调用者, 来让我们的API易于使用且易于维护。

Go 测试

Go 中的测试代码通常写在源代码的旁边, 以xxx_test.go命名。例如我们的源码文件叫做sum.go,测试代码应该写 在sum_test.go 中。

在测试代码中命名package的时候,我们有两个选择。

  1. 使用和源码文件相同的package名,例如源码文件和测试文件都是package math
  2. 使用和源码文件不同的package名,通常是xxx_test。例如源码文件是package math,测试文件是package math_test

通常情况下,我们会选择第二种命名方式。因为测试文件和源码文件使用不同的package,被测代码就是一个黑盒,能够让我们专注地基于其 API(以大写字母开头的导出标识符) 进行开发,以帮助我们设计出更易用的 API 出来。

export_test.go

源码文件和测试文件使用不同的package名称,通常情况都是可以的,但在一些特殊 TestCase 下我们可能需要使用未导出的标识符,此时就需要export_test.go文件登场了。

整理一下,我们的需求是这样的:

  1. 不将未导出标识符导出到生产代码中
  2. 将一些未导出标识符导出到测试代码中

此时我们就可以将一些未导出标识符包裹成导出标识符,放到export_test.go文件中,供测试代码使用。

export_test.go 文件仅仅在运行go test命令的时候会被包括进来,所以它不会污染包所提供的API,用户也无法访问到它(不像 Java 中的@VisibleForTesting)。

它建立了一座桥梁,让测试代码能够访问到未导出的标识符。

export_test.go这个名字不是限定死的,也可以使用其他名字,但注意必须以_test.go结尾。

使用 export_test.go 的实例

  • math的目录结构如下:
1
2
3
4
math
├── export_test.go
├── math.go
└── math_test.go
  • sum.go文件中有未导出标识符sum函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//sum.go
package math

func sum(a, b int) int {
    return a + b
}

func Name() {

}
  • export_test.go文件命名了Sum函数作为sum函数的副本
1
2
3
4
//export_test.go
package math

var Sum = sum
  • sum_test.go测试文件使用的包名是package math_test,它通过export_test.go访问到了math未导出的标识符Sum
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//sum_test.go
package math_test

import (
    "github.com/d5/tengo/assert"
    "testing"
    "xxx/math"
)

func TestName(t *testing.T) {
    assert.Equal(t, 3, math.Sum(1, 2))
}

Go 标准库中的应用

在 Go 1.12的 strings 库中,存在以下定义

  1. search.go中定义的函数makeStringFinder是未导出的。
  2. search_test.gosearch.go使用了不同的package名称,分别是stringsstrings_test
  3. 为了让search_test.go中能够使用到makeStringFinder函数,在export_test.go中定义了StringFind函数,供其使用。

以下是代码链接,大家可以参考阅读。