为什么使用初始化函数创建的结构体不能导出到测试包中?

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

What don't structs created with a initilzer function get exported to test packages?

问题

假设我们有一个结构体和一个用于该结构体的构造函数,代码如下:

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() {
    // ...
}

我们在一个测试包中使用这个客户端,代码如下:

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

func TestUnexported(t *testing.T) {
    c.privateFunc() // 可以正常工作
}

在上述情况下,私有方法在测试文件中被正常发现。但是,当我们像这样使用构造函数时:

var (
    c = NewClient()
)

func TestUnexported(t *testing.T) {
    c.privateFunc() // 无法工作
}

私有方法不会被暴露给测试。这些文件存在于同一个包中,并遵循*_test的命名模式。我还没有完全弄清楚发生了什么作用域方面的问题,为什么通过构造函数创建的私有方法会被隐藏,而通过常规构造方法则不会。

英文:

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

答案1

得分: 1

当你将一个类型作为接口返回或在函数中接受一个接口时,该类型将被缩减为仅包含该接口中的方法,其他所有内容将无法访问。你已经有效地将类型从Client转换为ConcreteClient

考虑一下,如果所有类型的所有方法都可以轻松访问,代码会是什么样子:

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

这样的函数使用起来会很烦人:“你可以传递任何io.Reader给它,但是哦,它还必须有Seek()方法,否则你会得到编译错误”。

这就是为什么会存在额外的接口,比如io.ReadSeeker(它具有Read()Seek()方法)。


并非一切都失去了,你仍然可以通过类型的方法访问一切:

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

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

或者你可以使用类型断言来获取原始类型:

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

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\n", cclient)
    os.Exit(1)
}
fmt.Printf("a: %v\n", client.a)

huangapple
  • 本文由 发表于 2017年8月26日 02:56:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/45887883.html
匿名

发表评论

匿名网友

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

确定