【问题标题】:How can I allow one package access to another package's unexported data only when testing?如何仅在测试时允许一个包访问另一个包的未导出数据?
【发布时间】:2017-08-13 08:17:02
【问题描述】:

Go 编程语言的第 11.2.4 节中,有一个外部测试通过 fmtexport_test.go 中的 IsSpace 声明访问 fmt.isSpace() 的示例文件。这似乎是一个完美的解决方案,所以我就是这样做的:

a/a.go:

package a

var x int

func Set(v int) {
    x = v
}

a/a_test.go:

package a
import "testing"

func TestSet(t *testing.T) {
    Set(42)
    if x != 42 {
        t.Errorf("x == %d (expected 42)", x)
    }
}

func Get() int {
    return x
}

(在a/ 中运行go test 工作正常。)

b/b.go:

package b
import "path/to/a"

func CallSet() {
    a.Set(105)
}

b/b_test.go

package b
import (
    "testing"
    "path/to/a"
)

func TestCallSet(t *testing.T) {
    CallSet()
    if r := a.Get(); r != 105 {
        t.Errorf("Get() == %d (expected 105)", r)
    }
}

不幸的是,当我在 b/ 中运行 go test 时,我得到:

./b_test.go:11: undefined: a.Get

尝试使用go test ./... 同时运行两组测试没有帮助。

经过相当多的探索后,我发现“The *_test.go files are compiled into the package only when running go test for that package”(强调我的)。 (所以,换句话说,我可以从a/ 中的a_test 外部测试包访问a.Get,但不能从a/ 之外的任何包访问。)

是否有其他方法可以允许来自一个包的测试访问另一个包的内部数据,以进行集成测试?

【问题讨论】:

  • 如果反对这个问题的人能提出如何改进的建议,我将不胜感激。
  • 您无法访问来自另一个包期间的未导出标识符。这是 Go 包的基本前提,所以这些测试示例没有什么不同。
  • This talk 关于测试 Go 代码,hashicorp 的创始人提供了一个您可能会觉得有用的解决方案。
  • 为什么要在集成测试中关心包的内部结构?这是黑盒测试的完美示例。顾名思义,其预期目的是测试系统不同部分的集成,您可以通过它们的公共接口访问这些特定部分。
  • @cpcallen 我不是 100% 确定,所以不要引用我的话,但我认为链接器会删除未使用的符号,因此在一些特殊情况下它们不是,例如,如果您在testing_*.go 中声明的func 仅在*_test.go 文件中使用,那么我怀疑它不会进入最终的可执行文件......

标签: testing go integration-testing


【解决方案1】:

如前所述,没有任何方法可以“授予”对未导出标识符的访问权限。

尽管需要/证明对 fmt 包的测试进行一些澄清。

有两种测试:黑盒测试和白盒测试。

黑盒测试是将包视为“黑盒”,并且仅通过其导出的标识符(通过其“公共 API”,其他包看到的)对其进行测试.在这种情况下,测试文件具有不同的包名称(例如,在测试 fmt 包时为 fmt_test)。

白盒测试是指同时使用包的导出和未导出标识符。要创建白盒测试,您需要在测试文件中指定与正在测试的包相同的包名称(因此,fmtfmt 包测试的情况下)。

标准库的fmt 包中包含的测试打算是一个黑盒测试,但它无法测试所有内容。因此,Go 作者选择了一个混合版本:他们包含一个使用相同包声明 (package fmt) 的单个 export_test.go 测试文件,因此它可以访问 fmt 包的未导出标识符,并且它“导出”2个标识符,以便其他(黑盒)测试文件可以访问:

var IsSpace = isSpace
var Parsenum = parsenum

作者这样做是因为他们想尽量减少未导出标识符的使用,因此这明确标记了使用了哪些未导出标识符,并且基本上充当了 fmt 包和黑色包之间的“桥梁” fmt 包的盒子测试。

这里要注意的一点是,这些只会“导出”到fmt 包的(黑盒)测试(当然还有fmt 的白盒测试),而不是其他包或其他软件包的测试。原因很简单,因为在构建包时不会解析和编译测试文件,只有在运行包测试时才会解析和编译。

解决方案?

未导出的标识符用于包本身,与其他人无关。这意味着任何其他软件包都不应该想要测试它们。如果需要对其进行测试,则必须在包自己的测试中完成。

如果您需要在集成测试中访问未导出的标识符,则包必须导出“某些东西”,这些“东西”要么公开值(或它们的某些部分),要么在不导出敏感数据或实现细节的情况下帮助测试。如果包 API 设计良好且测试彻底,则永远(很少)需要这样做。

【讨论】:

  • 黑盒与白盒测试的描述可能对那些不熟悉这些概念的人有用。有时——尤其是在对相关包进行集成测试时——如果他们可以直接检查另一个包的内部状态,测试可以变得更加简单。
  • @cpcallen 我仍然认为这不值得。如果您更改包的内部结构(包括未导出的标识符)而保持公共 API 不变,那应该会影响 no one。如果集成测试可以访问——并因此依赖——包的内部结构(未导出的标识符),那么当包内部结构发生变化时可能会不合理地中断。
  • 您说白盒测试会变得更加脆弱是绝对正确的,但我认为我有权决定何时进行,何时不值得。在某些情况下,它可以让测试变得更简单,而且一个非常简单但理论上很脆弱的测试可能比一个非常复杂的测试更可取,它对我从未做过的更改具有鲁棒性。
  • @cpcallen 然后简单地添加一个导出函数来暴露该值,并注释它仅用于测试目的,不应用于其他用途。
【解决方案2】:

是否有其他方法可以允许来自一个包的测试访问另一个包的内部数据,以进行集成测试?

没有。没有。

【讨论】:

    猜你喜欢
    • 2013-09-17
    • 1970-01-01
    • 2014-09-03
    • 2020-02-24
    • 2016-07-29
    • 1970-01-01
    • 2021-01-26
    • 2019-05-02
    • 2019-03-21
    相关资源
    最近更新 更多