<commit_before>### 11.2.4. 擴展測試包

TODO<commit_msg>Update ch11-02-4.md<commit_after>### 11.2.4. 擴展測試包

考虑下这两个包: net/url 包, 提供了 URL 解析的功能; net/http 包, 提供了web服务和HTTP客户端的功能. 如我们所料, 上层的 net/http 包依赖下层的 net/url 包. 然后, net/url 包中的一个测试是演示不同URL和HTTP客户端的交互行为. 也就是说, 一个下层包的测试代码导入了上层的包.

![](../images/ch11-01.png)

这样的行为在 net/url 包的测试代码中会导致包的循环依赖, 正如 图11.1中向上箭头所示, 同时正如我们在 10.1节所说, Go语言规范是禁止包的循环依赖的.

我们可以通过测试扩展包的方式解决循环依赖的问题, 也就是在 net/url 包所在的目录声明一个 url_test 测试扩展包. 其中测试扩展包名的 `_test` 后缀告诉 go test 工具它应该建立一个额外的包来运行测试. 我们将这个扩展测试包的导入路径视作是 net/url_test 会更容易理解, 但实际上它并不能被其他任何包导入.

因为测试扩展包是一个独立的包, 因此可以导入测试代码依赖的其他的辅助包; 包内的测试代码可能无法做到. 在设计层面, 测试扩展包是在所以它依赖的包的上层, 正如 图11.2所示.

![](../images/ch11-02.png)

通过回避循环导入依赖, 扩展测试包可以更灵活的测试, 特别是集成测试(需要测试多个组件之间的交互), 可以像普通应用程序那样自由地导入其他包.

我们可以用 go list 工具查看包对应目录中哪些Go源文件是产品代码, 哪些是包内测试, 还哪些测试扩展包. 我们以 fmt 包作为一个例子. GoFiles 表示产品代码对应的Go源文件列表; 也就是 go build 命令要编译的部分:

```
$ go list -f={{.GoFiles}} fmt
[doc.go format.go print.go scan.go]
```

TestGoFiles 表示的是 fmt 包内部测试测试代码, 以 _test.go 为后缀文件名, 不过只在测试时被构建:

```
$ go list -f={{.TestGoFiles}} fmt
[export_test.go]
```

包的测试代码通常都在这些文件中, 不过 fmt 包并非如此; 稍后我们再解释 export_test.go 文件的作用.

XTestGoFiles 表示的是属于测试扩展包的测试代码, 也就是 fmt_test 包, 因此它们必须先导入 fmt 包. 同样, 这些文件也只是在测试时被构建运行:

```
$ go list -f={{.XTestGoFiles}} fmt
[fmt_test.go scan_test.go stringer_test.go]
```

有时候测试扩展包需要访问被测试包内部的代码, 例如在一个为了避免循环导入而被独立到外部测试扩展包的白盒测试. 在这种情况下, 我们可以通过一些技巧解决: 我们在包内的一个 _test.go 文件中导出一个内部的实现给测试扩展包. 因为这些代码只有在测试时才需要, 因此一般放在 export_test.go 文件中.

例如, fmt 包的 fmt.Scanf 需要 unicode.IsSpace 函数提供的功能. 但是为了避免太多的依赖, fmt 包并没有导入包含巨大表格数据的 unicode 包; 相反fmt包有一个叫 isSpace 内部的简易实现.

为了确保 fmt.isSpace 和 unicode.IsSpace 函数的行为一致, fmt 包谨慎地包含了一个测试. 是一个在测试扩展包内的测试, 因此是无法直接访问到 isSpace 内部函数的, 因此 fmt 通过一个秘密出口导出了 isSpace 函数. export_test.go 文件就是专门用于测试扩展包的秘密出口.

```Go
package fmt

var IsSpace = isSpace
```

这个测试文件并没有定义测试代码; 它只是通过 fmt.IsSpace 简单导出了内部的 isSpace 函数, 提供给测试扩展包使用. 这个技巧可以广泛用于位于测试扩展包的白盒测试.

{'fraction_non_alphanumeric': 0.1049317943336831, 'fraction_numerical': 0.015739769150052464, 'mean_word_length': 5.4425675675675675, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7613126', 'n_tokens_mistral': 1445, 'n_tokens_neox': 1704, 'n_words': 182}