How can I allow one package access to another pack

2019-09-14 18:30发布

问题:

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:

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.



回答2:

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

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.

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.