有一种方法可以使用反射读取未导出的成员(在 Go < 1.7 中)
func read_foo(f *Foo) {
v := reflect.ValueOf(*f)
y := v.FieldByName("y")
fmt.Println(y.Interface())
}
但是,尝试使用 y.Set 或以其他方式使用反射设置字段将导致代码恐慌,您试图在包外设置未导出的字段。
简而言之:未导出的字段应该出于某种原因取消导出,如果您需要更改它们,请将需要更改的内容放在同一个包中,或者公开/导出一些安全的方式来更改它。
也就是说,为了完全回答问题,您可以这样做(并且必须在 Go >= 1.7 中这样做)
func change_foo(f *Foo) {
// Since structs are organized in memory order, we can advance the pointer
// by field size until we're at the desired member. For y, we advance by 8
// since it's the size of an int on a 64-bit machine and the int "x" is first
// in the representation of Foo.
//
// If you wanted to alter x, you wouldn't advance the pointer at all, and simply
// would need to convert ptrTof to the type (*int)
ptrTof := unsafe.Pointer(f)
ptrTof = unsafe.Pointer(uintptr(ptrTof) + uintptr(8)) // Or 4, if this is 32-bit
ptrToy := (**Foo)(ptrTof)
*ptrToy = nil // or *ptrToy = &Foo{} or whatever you want
}
这是一个非常非常糟糕的主意。它不是可移植的,如果 int 的大小发生变化,它将失败,如果你重新排列 Foo 中字段的顺序,更改它们的类型或大小,或者在预先存在的字段之前添加新字段,这个函数会很高兴地改变随机乱码数据的新表示,无需告诉您。我也认为它可能会破坏这个块的垃圾收集。
请,如果您需要从包外部更改字段,请编写功能以从包内更改它或导出它。
Edit2:由于您提到白盒测试,请注意,如果您在目录中命名文件,<whatever>_test.go
除非您使用,否则它不会编译go test
,因此如果您想做白盒测试,请在顶部声明package <yourpackage>
这将使您可以访问未导出的字段,如果你想进行黑盒测试,那么你使用package <yourpackage>_test
.
但是,如果您需要同时对两个包进行白盒测试,我认为您可能会遇到困难,可能需要重新考虑您的设计。