英文:
How can I allow one package access to another package's unexported data only when testing?
问题
《Go编程语言》第11.2.4节中有一个示例,展示了外部测试通过在fmt
的export_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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论