如何模拟 net.Interface?

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

How to mock net.Interface

问题

我正在尝试在Go中模拟net.Interface,我使用net.Interfaces(),并且希望有一个固定的返回值。但是net.Interface不是一个接口,所以我无法使用gomock来模拟它。

也许我在测试的方式上有些错误。

这是我想要测试的方法:

  1. const InterfaceWlan = "wlan0"
  2. const InterfaceEthernet = "eth0"
  3. var netInterfaces = net.Interfaces
  4. func GetIpAddress() (net.IP, error) {
  5. // 获取接口列表
  6. ifaces, err := netInterfaces()
  7. if err != nil {
  8. return nil, err
  9. }
  10. // 遍历接口列表
  11. for _, i := range ifaces {
  12. // 只关注Wlan0或Eth0接口
  13. if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
  14. // 获取关联的IP地址(通常是IPv4和IPv6)
  15. addrs, err := i.Addrs()
  16. // 对addrs进行一些处理...
  17. }
  18. }
  19. return nil, errors.New("network: ip not found")
  20. }

这是我目前编写的测试:

  1. func TestGetIpAddress(t *testing.T) {
  2. netInterfaces = func() ([]net.Interface, error) {
  3. // 我可以创建net.Interface{},但是我无法重新定义net.Interface上的`Addrs`方法
  4. }
  5. address, err := GetIpAddress()
  6. if err != nil {
  7. t.Errorf("GetIpAddress: error = %v", err)
  8. }
  9. if address == nil {
  10. t.Errorf("GetIpAddress: errror = address ip is nil")
  11. }
  12. }

最小可复现示例:

英文:

I'm trying to mock net.Interface in Go, I use net.Interfaces() and I want to have a fixed return. But net.Interface is not an interface, so I can't mock it with gomock.

Maybe I'm wrong in the way I test.

Here is the method I want to test:

  1. const InterfaceWlan = "wlan0"
  2. const InterfaceEthernet = "eth0"
  3. var netInterfaces = net.Interfaces
  4. func GetIpAddress() (net.IP, error) {
  5. // On récupère la liste des interfaces
  6. ifaces, err := netInterfaces()
  7. if err != nil {
  8. return nil, err
  9. }
  10. // On parcours la liste des interfaces
  11. for _, i := range ifaces {
  12. // Seul l'interface Wlan0 ou Eth0 nous intéresse
  13. if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
  14. // On récupère les adresses IP associé (généralement IPv4 et IPv6)
  15. addrs, err := i.Addrs()
  16. // Some treatments on addrs...
  17. }
  18. }
  19. return nil, errors.New("network: ip not found")
  20. }

Here is the test I wrote for the moment

  1. func TestGetIpAddress(t *testing.T) {
  2. netInterfaces = func() ([]net.Interface, error) {
  3. // I can create net.Interface{}, but I can't redefine
  4. // method `Addrs` on net.Interface
  5. }
  6. address, err := GetIpAddress()
  7. if err != nil {
  8. t.Errorf("GetIpAddress: error = %v", err)
  9. }
  10. if address == nil {
  11. t.Errorf("GetIpAddress: errror = address ip is nil")
  12. }
  13. }

Minimal reproductible example:

答案1

得分: 5

你可以使用方法表达式将方法绑定到函数类型的变量上,就像你已经将net.Interfaces函数绑定到变量上一样:

  1. var (
  2. netInterfaces = net.Interfaces
  3. netInterfaceAddrs = (*net.Interface).Addrs
  4. )
  5. func GetIpAddress() (net.IP, error) {
  6. // 获取IP地址(模拟方法Addrs?)
  7. addrs, err := netInterfaceAddrs(&i)
  8. }

然后,在测试中,你可以以相同的方式更新绑定:

  1. func TestGetIpAddress(t *testing.T) {
  2. netInterfaceAddrs = func(i *net.Interface) ([]net.Addr, error) {
  3. return []net.Addr{}, nil
  4. }
  5. }

(https://play.golang.org/p/rqb0MDclTe2)


话虽如此,我建议将模拟的方法提取到一个结构类型中,而不是覆盖全局变量。这样可以使测试并行运行,并且允许你包的下游用户编写自己的测试而不会改变全局状态。

  1. // NetEnumerator 枚举本地IP地址。
  2. type NetEnumerator struct {
  3. Interfaces func() ([]net.Interface, error)
  4. InterfaceAddrs func(*net.Interface) ([]net.Addr, error)
  5. }
  6. // DefaultEnumerator 返回一个使用net包中默认实现的NetEnumerator。
  7. func DefaultEnumerator() NetEnumerator {
  8. return NetEnumerator{
  9. Interfaces: net.Interfaces,
  10. InterfaceAddrs: (*net.Interface).Addrs,
  11. }
  12. }
  13. func GetIpAddress(e NetEnumerator) (net.IP, error) {
  14. }

(https://play.golang.org/p/PLIXuOpH3ra)

英文:

You can use a method expression to bind the method to a variable of function type, in much the same way that you are already binding the net.Interfaces function to a variable:

  1. var (
  2. netInterfaces = net.Interfaces
  3. netInterfaceAddrs = (*net.Interface).Addrs
  4. )
  5. func GetIpAddress() (net.IP, error) {
  6. // Get IPs (mock method Addrs ?)
  7. addrs, err := netInterfaceAddrs(&i)
  8. }

Then, in the test, you can update the binding in the same way:

  1. func TestGetIpAddress(t *testing.T) {
  2. netInterfaceAddrs = func(i *net.Interface) ([]net.Addr, error) {
  3. return []net.Addr{}, nil
  4. }
  5. }

(https://play.golang.org/p/rqb0MDclTe2)


That said, I would recommend factoring out the mocked methods into a struct type instead of overwriting global variables. That allows the test to run in parallel, and also allows downstream users of your package to write their own tests without mutating global state.

  1. // A NetEnumerator enumerates local IP addresses.
  2. type NetEnumerator struct {
  3. Interfaces func() ([]net.Interface, error)
  4. InterfaceAddrs func(*net.Interface) ([]net.Addr, error)
  5. }
  6. // DefaultEnumerator returns a NetEnumerator that uses the default
  7. // implementations from the net package.
  8. func DefaultEnumerator() NetEnumerator {
  9. return NetEnumerator{
  10. Interfaces: net.Interfaces,
  11. InterfaceAddrs: (*net.Interface).Addrs,
  12. }
  13. }
  14. func GetIpAddress(e NetEnumerator) (net.IP, error) {
  15. }

(https://play.golang.org/p/PLIXuOpH3ra)

答案2

得分: 1

IMO. 你可以将函数net.Interface和函数getAddrs注入到GetIpAddress中,以获取[]net.Addrs

  1. type NetworkHandler struct {
  2. GetInterfaces func() ([]net.Interface, error)
  3. GetAddrsFromInterface func(p net.Interface) ([]net.Addr, error)
  4. }
  5. func GetIpAddress(networkHandler NetworkHandler) (net.IP, error) {
  6. // 获取接口列表
  7. ifaces, err := networkHandler.GetInterfaces()
  8. if err != nil {
  9. return nil, err
  10. }
  11. // 遍历接口列表
  12. for _, i := range ifaces {
  13. // 只关注Wlan0或Eth0接口
  14. if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
  15. // 获取关联的IP地址(通常是IPv4和IPv6)
  16. _, err := networkHandler.GetAddrsFromInterface(i)
  17. // 处理错误
  18. if err != nil {
  19. return nil, err
  20. }
  21. // 对地址进行一些处理...
  22. }
  23. }
  24. return nil, errors.New("network: ip not found")
  25. }

测试代码

  1. func TestGetIpAddress(t *testing.T) {
  2. t.Run("should return error when cannot get address from net.interface", func(t *testing.T) {
  3. result, err := GetIpAddress(NetworkHandler{
  4. GetInterfaces: mockGetInterfaces,
  5. GetAddrsFromInterface: mockGetAddrs,
  6. })
  7. assert.Nil(t, result)
  8. assert.Error(t, err)
  9. assert.Equal(t, "cannot get addrs", err.Error())
  10. })
  11. }
  12. func mockGetInterfaces() ([]net.Interface, error) {
  13. return []net.Interface{
  14. {Name: "wlan0"},
  15. {Name: "eth0"}}, nil
  16. }
  17. // 当调用net.Interface{}.Addrs()时的存根行为
  18. func mockGetAddrs(i net.Interface) ([]net.Addr, error) {
  19. return nil, errors.New("cannot get addrs")
  20. }

用法示例

  1. func main() {
  2. GetIpAddress(NetworkHandler{
  3. GetInterfaces: net.Interfaces,
  4. GetAddrsFromInterface: func(p net.Interface) ([]net.Addr, error) {
  5. return p.Addrs()
  6. },
  7. })
  8. }
英文:

IMO. you can injection function net.Interface And function getAddrs for get []net.Addrs into GetIpAddress

  1. type NetworkHandler struct {
  2. GetInterfaces func() ([]net.Interface,error)
  3. GetAddrsFromInterface func(p net.Interface) ([]net.Addr,error)
  4. }
  5. func GetIpAddress(networkHandler NetworkHandler) (net.IP,error) {
  6. // On récupère la liste des interfaces
  7. ifaces, err := networkHandler.GetInterfaces()
  8. if err != nil {
  9. return nil, err
  10. }
  11. // On parcours la liste des interfaces
  12. for _, i := range ifaces {
  13. // Seul l'interface Wlan0 ou Eth0 nous intéresse
  14. if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
  15. // On récupère les adresses IP associé (généralement IPv4 et IPv6)
  16. _, err := networkHandler.GetAddrsFromInterface(i)
  17. // ex
  18. if err != nil {
  19. return nil, err
  20. }
  21. // Some treatments on addrs...
  22. }
  23. }
  24. return nil, errors.New("network: ip not found")
  25. }

ON Test code

  1. func TestGetIpAddress(t *testing.T) {
  2. t.Run("should return error when cannot get address from net.interface", func(t *testing.T) {
  3. result, err := GetIpAddress(NetworkHandler{
  4. GetInterfaces: mockGetInterfaces,
  5. GetAddrsFromInterface: mockGetAddrs,
  6. })
  7. assert.Nil(t, result)
  8. assert.Error(t, err)
  9. assert.Equal(t, "cannot get addrs",err.Error())
  10. })
  11. }
  12. func mockGetInterfaces() ([]net.Interface,error) {
  13. return []net.Interface{
  14. {Name: "wlan0"},
  15. {Name: "eth0"}},nil
  16. }
  17. // stub behavior when calling net.Interface{}.Addrs()
  18. func mockGetAddrs(i net.Interface) ([]net.Addr,error) {
  19. return nil, errors.New("cannot get addrs")
  20. }

on usage

  1. func main() {
  2. GetIpAddress(NetworkHandler{
  3. GetInterfaces: net.Interfaces,
  4. GetAddrsFromInterface: func(p net.Interface) ([]net.Addr, error) {
  5. return p.Addrs()
  6. },
  7. })
  8. }

huangapple
  • 本文由 发表于 2021年8月26日 22:15:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/68940230.html
匿名

发表评论

匿名网友

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

确定