运行测试时设置私有字段状态。

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

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 data a.
  • 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可能引发的黑洞或时间异常负责。

你可以在这里找到我的playground测试。

英文:

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.

You can find my playground test here.

huangapple
  • 本文由 发表于 2022年8月9日 16:23:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/73288644.html
匿名

发表评论

匿名网友

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

确定