当进行测试时,如何允许一个包访问另一个包的未导出数据?

huangapple go评论78阅读模式
英文:

How can I allow one package access to another package's unexported data only when testing?

问题

《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 ./...同时运行两组测试也没有帮助。

经过一番探索,我发现“仅当运行特定包的go test时,*_test.go文件才会被编译到该包中”(我强调的部分)。换句话说,我可以从a/中的a_test外部测试包中访问a.Get,但不能从a/之外的任何包中访问。

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

英文:

In The Go Programming Language, section 11.2.4, there is an example of of an external test accessing fmt.isSpace() via a declaration of IsSpace in fmt's export_test.go file. That seemed like the perfect solution, so that's what I did:

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
}

(Running go test in a/ works fine.)

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)
    }
}

Unfortunately, when I run go test in b/, I get:

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

Trying to run both sets of test at the same time with go test ./... did not help.

After some considerable poking around I discover that "The *_test.go files are compiled into the package only when running go test for that package" (emphasis mine). (So, in other words I could access a.Get from an a_test external test package in a/, but not from any package outside of a/.)

Is there some other way I can allow tests from one package to access otherwise-internal data from another package, for integration testing purposes?

答案1

得分: 6

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

然而,对于fmt包的测试,需要对其进行一些澄清/解释。

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

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

白盒测试是指同时使用包的导出和未导出标识符进行测试。为了创建白盒测试,测试文件中指定的包名称与正在测试的包相同(因此,在测试fmt包时,包名称为fmt)。

标准库的fmt包中包含的测试旨在进行黑盒测试,但这样将无法测试所有内容。因此,Go的作者选择了一种混合版本:他们包含了一个名为export_test.go的测试文件,该文件使用相同的包声明(package fmt),因此可以访问fmt包的未导出标识符,并且“导出”了两个标识符,以便其他(黑盒)测试文件可以访问:

var IsSpace = isSpace
var Parsenum = parsenum

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

需要注意的一点是,这些标识符仅对fmt包的(黑盒)测试(以及fmt的白盒测试)“导出”,而不对其他包或其他包的测试“导出”。原因很简单,即在构建包时,测试文件不会被解析和编译,只有在运行包测试时才会。

解决方案?

未导出的标识符是供包本身使用的,其他人无需关心。这意味着其他包永远不应该想要测试它们。如果需要对其进行测试,必须在包自己的测试中完成。

如果在集成测试中需要访问未导出的标识符,那么该包必须导出“某些东西”,以公开值(或其中的某些部分)或在不导出敏感数据或实现细节的情况下辅助测试。如果包的API设计良好且其测试全面,这应该很少(或几乎不会)需要。

英文:

As mentioned, there isn't any way to "grant" access to unexported identifiers.

Some clarification on the fmt package's tests is needed / justified though.

There are 2 kinds of testing: black-box testing and white-box testing.

Black-box testing is when you treat the package as a "black-box", and you test it only through its exported identifiers (through its "public API", what other packages see). In such cases, the test files have a different package name (e.g. fmt_test when testing the fmt package).

White-box testing is when you make use of both exported and unexported identifiers of the package. To create white-box tests, you specify the same package name in the test files as the package being tested (thus, fmt in case of the fmt package test).

The tests included in the standard lib's fmt package intend to be a black-box test, but then it would not be able to test everything. So the Go authors went for a mixed version: they included a single export_test.go test file which uses the same package declaration (package fmt) so it gets access to unexported identifiers of the fmt package, and it "exports" 2 identifiers so that the other (black-box) test files will have access to:

var IsSpace = isSpace
var Parsenum = parsenum

<sup>The authors did so because they wanted to minimize the use of unexported identifiers, and so this explicitly marks what unexported identifiers are used, and basically acts as a "bridge" between the fmt package and the black-box tests of the fmt package.</sup>

One thing to note here is that these will only be "exported" to the (black-box) tests of the fmt package (and white-box tests of fmt of course), and not for other packages or tests of other packages. The reason is simply because test files are not parsed and compiled when a package is built, only when package test is run.

Solution?

Unexported identifiers are for the package itself and they are no one else's business. Which means no other package should ever want to test them. If they need to be tested, it must be done inside the package's own tests.

If for integration testing you need to access unexported identifiers, then the package must export "something" that either exposes the values (or some parts of them), or aids the testing without exporting the sensitive data or implementation details. If the package API is well designed and its tests are thorough, this should never (rarely) be needed.

答案2

得分: 2

有没有其他方法可以让一个包中的测试代码访问另一个包中的内部数据,以进行集成测试目的?

没有,没有其他方法。

英文:

> Is there some other way I can allow tests from one package to access otherwise-internal data from another package, for integration testing purposes?

No. There isn't.

huangapple
  • 本文由 发表于 2017年3月21日 23:08:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/42930944.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定