英文:
go test setting private field state
问题
一个库包含一个导出方法A
的结构体类型,该方法是基于未导出字段a
的计算结果。
由于a
是未导出的,直接构造该类型将导致字段a
为空。
唯一设置a
的地方是当库以不容易使用导出的函数/字段实例化类型时(例如,数据是通过网络API调用填充的)。
我想要编写测试的程序使用DoLibraryThing
,并且特别依赖并操作S.A()
的结果。
我已经模拟了返回此类型的库Client
,但无法返回一个适当的实例,以便调用A
将产生有用的数据,以便在测试的其余部分中使用。被测试的函数还对Client/库进行了许多其他调用。
**是否有一种方法可以构造具有用于填充有用数据的a
的类型,或者以其他方式替换可以从A
返回有用数据的模拟S
?**由于从模拟返回时它不是*S
,因此使Client模拟返回一个导出了A
的不同结构体并具有硬编码行为是行不通的。
考虑的替代方案:
- 在此库调用点处将函数拆分,测试调用之前和之后的行为,并在测试函数的后半部分将
A
的结果作为测试输入提供给测试。在这里拆分函数只对测试有意义,并且涉及将第一部分函数实现的状态传递给第二部分函数实现的不必要复杂性。
func testMe(client I) {
kludge, data := testMe1(client)
return testMe2(kludge, data)
}
func testMe1(client I) {
// do a bunch of things with the client interface
s := client.DoLibraryThing()
data := s.A()
return Kludge{ /* pack up all the state */ }, data
}
func testMe2(k Kludge, d data) {
// unpack all the state
// do more things with client interface and data
}
^ 现在可以独立测试testMe1和testMe2,并且在测试testMe2时可以传入预定的测试数据。
英文:
- A library contains a struct type that exports method
A
which is computed based on unexported field dataa
. - Since
a
is unexported, directly constructing the type will yield empty field data. - The only place
a
is set is when the library instantiates the type in ways not easilsy replicable using exported functions/fields (e.g. data is populated from network API calls)
type S struct {
a []byte
}
func (s *S) A() ... {
// produces a meaningful result using the blob in field a
}
...
type I interface {
DoLibraryThing() (*S)
...
}
struct Client type {...}
func (c *Client) DoLibraryThing() (*S) {
...
}
...
The program I want to write tests for uses DoLibraryThing
and notably depends on and manipulates the result of S.A()
func testMe(client I) {
// do a bunch of things with the client interface
s := client.DoLibraryThing()
data := s.A()
// do more things with client interface and data
}
I have mocked the library Client
that returns this type, but am unable to return an appropriate instance such that calling A
will yield useful data to be used in the remainder of the test. The function being tested makes a number of other calls to the Client/library.
Is there a way to construct the type with a
seeded with useful data or otherwise substitute a mock S
that can return useful data from A
? Having the Client mock return a different struct that exports A
with hardcoded behavior doesn't work since it's not a *S
when being returned from the mock.
Alternatives considered:
- Split the function at the point of this library call, testing the behavior before and after the call, and providing the results of
A
to the test(s) as test inputs when testing the latter portion of the function. Splitting the function here would only really be meaningful for test and would involve the otherwise needless complexity of passing state from first part of function implementation to second part of function implementation.
func testMe(client I) {
kludge, data := testMe1(client)
return testMe2(kludge, data)
}
func testMe1(client I) {
// do a bunch of things with the client interface
s := client.DoLibraryThing()
data := s.A()
return Kludge{ /* pack up all the state */ }, data
}
func testMe2(k Kludge, d data) {
// unpack all the state
// do more things with client interface and data
}
^ testMe1 and testMe2 can now be independently tested and predetermined test data can be passed in when testing testMe2.
答案1
得分: 1
这是一个设置结构体字段值的函数,包括未导出的字段。我以这个为基础进行了编写。我不建议在除了测试之外的任何地方使用这个函数,只有在没有其他方法设置测试数据的情况下才使用。例如,在依赖项是无法编辑的第三方包的情况下。请注意,如果外部包发生更改,测试可能会出错。
以下是使用示例:链接
英文:
Here is a function that sets the value of a structure field, including the unexported field. I took this as a basis https://stackoverflow.com/a/43918797/3201891
I do not recommend using this function anywhere other than tests. And only if there is no other way to set test data. For example, in the case where the dependency is a third party package that you cannot edit. Be aware that tests may break if the external package changes.
func setFieldValue(target any, fieldName string, value any) {
rv := reflect.ValueOf(target)
for rv.Kind() == reflect.Ptr && !rv.IsNil() {
rv = rv.Elem()
}
if !rv.CanAddr() {
panic("target must be addressable")
}
if rv.Kind() != reflect.Struct {
panic(fmt.Sprintf(
"unable to set the '%s' field value of the type %T, target must be a struct",
fieldName,
target,
))
}
rf := rv.FieldByName(fieldName)
reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem().Set(reflect.ValueOf(value))
}
Here is usage example https://go.dev/play/p/i9w0RPcmKHc
答案2
得分: 0
我认为你需要使用 unsafe。
为此,你需要能够阅读你的库的源代码。
// LIB
type useful struct {
exported bool
unexported int
}
var s useful = useful{unexported: 3}
未导出的字段位于布尔值之后,在我的计算机上使用了8位。
你需要做的是获取指向s的指针,将其左移8位,并读取此时的值。
// 你的代码
func main() {
fmt.Println(unsafe.Offsetof(s.unexported)) // 我们得到8,在你的情况下需要尝试和调整
point := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + 8))
fmt.Println(*point)
}
作为一个经验丰富的旅行者,我要提醒你一点。这个包被称为unsafe,有一个非常好的原因。不要在任何代价超过CRTL+C的东西上尝试这个。你得到的值可能因计算机而异,特别是如果你使用的是专用于特定用途的机器。我不对任何unsafe可能引发的黑洞或时间异常负责。
英文:
I think you need to go unsafe.
For this you will need the ability to read the source-code of your library.
// LIB
type useful struct {
exported bool
unexported int
}
var s useful = useful{unexported: 3}
The unexported field is after a boolean value, which uses in my computer 8 bits.
What you have to do, would be to get a pointer to s, shift it by the 8 bits, and read the value at this moment.
// Your side
func main() {
fmt.Println(unsafe.Offsetof(s.unexported)) // we get 8, it's try and error in your case
point := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + 8))
fmt.Println(*point)
}
A bit of warning from a weary traveler. The package is called unsafe for a very good reason. Do not try this on anything that would cost more than a CRTL+C to fix. The values you get may vary from computer to computer, especially if you use a machine that is for niche purposes. I will not be held liable for any black hole or time anomaly that unsaffe unleashes.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论