使用initilzer函数创建的结构不会导出到测试包中?

Say we have a struct and a constructor function for the structure like such

package myPackage

type Client struct {
    a TypeA
    b TypeB
}

func NewClient(a TypeA, b TypeB) ConcreteClient {
    return &Client{
        a: a,
        b: b,
    }
}

type ConcreteClient interface {
    ExportedFunc()
}

func (c *Client) privateFunc() {
    // ...
}

func (c *Client) ExportedFunc() {
    // ...
}

And we use this client in a test package like such

var (
    c = &Client {
        a:a,
        b:b,
    }
)

func TestUnexported(t *testing.T) {
    c.privateFunc() // Works
}

In the previous case the unexported is discovered as expected in the test file but when we use the constructor like this

var (
    c = NewClient()
)

func TestUnexported(t *testing.T) {
    c.privateFunc() // Doesn't work
}

The unexported method isn't exposed for testing. These files exist within the same package and follow the *_test naming pattern.I haven't been able to figure out exactly what is going on scoping wise as to why the unexported methods are hidden when created through a constructor and not through typical construction.

  • Edited for clarity

When you return a type as an interface or accept an interface in a function, then the type is reduced to only the methods in that interface and everything else will be inaccessible. You've effective "converted" the type from Client to ConcreteClient.

Consider how the code would look if all methods from all types be readily accessible:

func f(fp io.Reader) {
    fp.Seek(42)
}

How annoying would such a function be to use: "you can pass any io.Reader to this, but oh, it must also have the Seek() method or you'll get compile errors".

This is why additional interfaces such as io.ReadSeeker (which has Read() and Seek()) exist.


Not all is lost, you can still use access everything from the type methods:

func (c *Client) ExportedFunc() string {
    return c.a
}

func main() {
    cclient := NewClient("this is a", "this is b")
    fmt.Printf(cclient.ExportedFunc())
}

Or you can use a type assertion to get the original type back:

cclient := NewClient("this is a", "this is b")
client, ok := cclient.(*Client)
if !ok {
    fmt.Printf("not a Client: %T
", cclient)
    os.Exit(1)
}
fmt.Printf("a: %v
", client.a)