这个属于奇技淫巧,不建议项目中使用。如果要用一定要带上注释。
最近在搞 coredns 的开发,代码嘎嘎敲完之后开始啃单元测试。
在写测试的时候,需要测试的某个函数,我们这里假设是 func abc(r request.Request)
,它接收 coredns 的 request.Request
类型,在内部调用的 r.Name()
r.IP()
获取请求域名和请求方 ip,但是对应单测文件的传入参数在我接手的时候写的还是字符串类型,估计好久没改过了。
这是 request.Request 的定义
1 |
|
想要测试这个函数,就需要向其中传入有效的 Request 结构体,但是问题是 name 和 ip 在里面都是小写字母开头,属于私有变量,也没有一个方便的方法可以在新建 Request 时初始化 name 和 ip。
为啥不用反射
写过 java,看到这个问题,第一个想法自然是使用反射来获取对应的成员赋值。但是理想和现实总有点差距,查了才知道,golang 中的反射限制有点多。
java 中反射的作用很强大,又可以取值又可以赋值。golang 中的反射只可以获取已知名称字段的值,如果想要给私有成员赋值就会抛出 panic 炸掉程序。(公有成员没事)
使用 unsafe 解决赋值问题
好在 golang 中还可以获取变量的指针,这样我们可以通过获取 Request 的 unsafe 指针来通过偏移量修改其成员的值。(奇技淫巧,如无必要,不宜使用)
获取偏移量
观察数据结构发现,name 和 ip 和结构体的尾部仅相差几个字符串的偏移量。因此采用修改 基址+sizeof(Request)-n*sizeof(string)
的格式来定位对应地址。
测试
先写个 demo 测试一下,鉴于 name 和 ip 分别是结构体倒数第五和第四个成员,因此下面 (unsafe.Sizeof(*new(string)))
需要分别乘 5 和乘 4
1 |
|
打上断点,直接开跑
可以看到,对应的私有变量已经被修改成我们想要的值