如何模拟 net.Interface?

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

How to mock net.Interface

问题

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

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

这是我想要测试的方法:

const InterfaceWlan = "wlan0"
const InterfaceEthernet = "eth0"

var netInterfaces = net.Interfaces

func GetIpAddress() (net.IP, error) {
	// 获取接口列表
	ifaces, err := netInterfaces()
	if err != nil {
		return nil, err
	}

	// 遍历接口列表
	for _, i := range ifaces {
		// 只关注Wlan0或Eth0接口
		if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
			// 获取关联的IP地址(通常是IPv4和IPv6)
			addrs, err := i.Addrs()

			// 对addrs进行一些处理...
		}
	}

	return nil, errors.New("network: ip not found")
}

这是我目前编写的测试:

func TestGetIpAddress(t *testing.T) {
	netInterfaces = func() ([]net.Interface, error) {
		// 我可以创建net.Interface{},但是我无法重新定义net.Interface上的`Addrs`方法
	}
	
	address, err := GetIpAddress()
	if err != nil {
		t.Errorf("GetIpAddress: error = %v", err)
	}

	if address == nil {
		t.Errorf("GetIpAddress: errror = address ip is nil")
	}
}

最小可复现示例:

英文:

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:

const InterfaceWlan = "wlan0"
const InterfaceEthernet = "eth0"

var netInterfaces = net.Interfaces

func GetIpAddress() (net.IP, error) {
	// On récupère la liste des interfaces
	ifaces, err := netInterfaces()
	if err != nil {
		return nil, err
	}

	// On parcours la liste des interfaces
	for _, i := range ifaces {
		// Seul l'interface Wlan0 ou Eth0 nous intéresse
		if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
			// On récupère les adresses IP associé (généralement IPv4 et IPv6)
			addrs, err := i.Addrs()

			// Some treatments on addrs...
		}
	}

	return nil, errors.New("network: ip not found")
}

Here is the test I wrote for the moment

func TestGetIpAddress(t *testing.T) {
	netInterfaces = func() ([]net.Interface, error) {
		// I can create net.Interface{}, but I can't redefine 
		// method `Addrs` on net.Interface
	}
	
	address, err := GetIpAddress()
	if err != nil {
		t.Errorf("GetIpAddress: error = %v", err)
	}

	if address == nil {
		t.Errorf("GetIpAddress: errror = address ip is nil")
	}
}

Minimal reproductible example:

答案1

得分: 5

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

var (
	netInterfaces     = net.Interfaces
	netInterfaceAddrs = (*net.Interface).Addrs
)

func GetIpAddress() (net.IP, error) {
	
			// 获取IP地址(模拟方法Addrs?)
			addrs, err := netInterfaceAddrs(&i)
	
}

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

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

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


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

// NetEnumerator 枚举本地IP地址。
type NetEnumerator struct {
	Interfaces     func() ([]net.Interface, error)
	InterfaceAddrs func(*net.Interface) ([]net.Addr, error)
}

// DefaultEnumerator 返回一个使用net包中默认实现的NetEnumerator。
func DefaultEnumerator() NetEnumerator {
	return NetEnumerator{
		Interfaces:     net.Interfaces,
		InterfaceAddrs: (*net.Interface).Addrs,
	}
}

func GetIpAddress(e NetEnumerator) (net.IP, error) {
	
}

(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:

var (
	netInterfaces     = net.Interfaces
	netInterfaceAddrs = (*net.Interface).Addrs
)

func GetIpAddress() (net.IP, error) {
	
			// Get IPs (mock method Addrs ?)
			addrs, err := netInterfaceAddrs(&i)
	
}

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

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

(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.

// A NetEnumerator enumerates local IP addresses.
type NetEnumerator struct {
	Interfaces     func() ([]net.Interface, error)
	InterfaceAddrs func(*net.Interface) ([]net.Addr, error)
}

// DefaultEnumerator returns a NetEnumerator that uses the default
// implementations from the net package.
func DefaultEnumerator() NetEnumerator {
	return NetEnumerator{
		Interfaces:     net.Interfaces,
		InterfaceAddrs: (*net.Interface).Addrs,
	}
}

func GetIpAddress(e NetEnumerator) (net.IP, error) {
	
}

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

答案2

得分: 1

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

type NetworkHandler struct {
	GetInterfaces func() ([]net.Interface, error)
	GetAddrsFromInterface func(p net.Interface) ([]net.Addr, error)
}
func GetIpAddress(networkHandler NetworkHandler) (net.IP, error) {
	// 获取接口列表
	ifaces, err := networkHandler.GetInterfaces()
	if err != nil {
		return nil, err
	}

	// 遍历接口列表
	for _, i := range ifaces {
		// 只关注Wlan0或Eth0接口
		if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
			// 获取关联的IP地址(通常是IPv4和IPv6)
			_, err := networkHandler.GetAddrsFromInterface(i)

			// 处理错误
			if err != nil {
				return nil, err
			}
			// 对地址进行一些处理...
		}
	}

	return nil, errors.New("network: ip not found")
}

测试代码

func TestGetIpAddress(t *testing.T) {
	t.Run("should return error when cannot get address from net.interface", func(t *testing.T) {
		result, err := GetIpAddress(NetworkHandler{
			GetInterfaces:         mockGetInterfaces,
			GetAddrsFromInterface: mockGetAddrs,
		})

		assert.Nil(t, result)
		assert.Error(t, err)
		assert.Equal(t, "cannot get addrs", err.Error())
	})
}
func mockGetInterfaces() ([]net.Interface, error) {
	return []net.Interface{
		{Name: "wlan0"},
		{Name: "eth0"}}, nil
}

// 当调用net.Interface{}.Addrs()时的存根行为
func mockGetAddrs(i net.Interface) ([]net.Addr, error) {
	return nil, errors.New("cannot get addrs")
}

用法示例

func main() {
	GetIpAddress(NetworkHandler{
		GetInterfaces:         net.Interfaces,
		GetAddrsFromInterface: func(p net.Interface) ([]net.Addr, error) {
			return p.Addrs()
		},
	})
}
英文:

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

type NetworkHandler struct {
	GetInterfaces func() ([]net.Interface,error)
	GetAddrsFromInterface func(p net.Interface) ([]net.Addr,error)
}
func GetIpAddress(networkHandler NetworkHandler) (net.IP,error) {
	// On récupère la liste des interfaces
	ifaces, err := networkHandler.GetInterfaces()
	if err != nil {
		return nil, err
	}

	// On parcours la liste des interfaces
	for _, i := range ifaces {
		// Seul l'interface Wlan0 ou Eth0 nous intéresse
		if i.Name == InterfaceWlan || i.Name == InterfaceEthernet {
			// On récupère les adresses IP associé (généralement IPv4 et IPv6)
			_, err := networkHandler.GetAddrsFromInterface(i)

			// ex
			if err != nil {
				return nil, err
			}
			// Some treatments on addrs...
		}
	}

	return nil, errors.New("network: ip not found")
}

ON Test code

func TestGetIpAddress(t *testing.T) {
	t.Run("should return error when cannot get address from net.interface", func(t *testing.T) {
		result, err := GetIpAddress(NetworkHandler{
			GetInterfaces:         mockGetInterfaces,
			GetAddrsFromInterface: mockGetAddrs,
		})

		assert.Nil(t, result)
		assert.Error(t, err)
		assert.Equal(t, "cannot get addrs",err.Error())
	})
}
func mockGetInterfaces() ([]net.Interface,error) {
	return []net.Interface{
		{Name:         "wlan0"},
		{Name:         "eth0"}},nil
}

// stub behavior when calling net.Interface{}.Addrs()
func mockGetAddrs(i net.Interface) ([]net.Addr,error) {
	return nil, errors.New("cannot get addrs")
}

on usage

func main() {
	GetIpAddress(NetworkHandler{
		GetInterfaces:         net.Interfaces,
		GetAddrsFromInterface: func(p net.Interface) ([]net.Addr, error) {
			return p.Addrs()
		},
	})
}

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:

确定