如何模拟 net.Interface

How to mock net.Interface

我试图在 Go 中模拟 net.Interface,我使用 net.Interfaces() 并且我想要一个固定的 return。但是 net.Interface 不是一个接口,所以我不能用 gomock 来模拟它。

可能我测试的方式不对

这是我要测试的方法:

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")
}

这是我暂时写的测试

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")
    }
}

最小可复制示例:

如果可以的话,把那个函数包装成一个类型,然后用它来安排 jit。

package main

import (
    "errors"
    "fmt"
    "net"
)

func main() {
    fmt.Println("Hello, playground")
    var myfn listInterfaces = func() ([]net.Interface, error) {
        return nil, fmt.Errorf("it will never happen")
    }
    addrs, err := myfn.GetIPAddress()
    fmt.Println(addrs, err)
}

type listInterfaces func() ([]net.Interface, error)

func (j listInterfaces) GetIPAddress() (net.IP, error) {
    // On récupère la liste des interfaces
    ifaces, err := j()
    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")
}

https://play.golang.org/p/G1ZKg8fvjro

您可以使用 method expression 将方法绑定到函数类型的变量,这与您已经将 net.Interfaces 函数绑定到变量的方式大致相同:

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

func GetIpAddress() (net.IP, error) {
    …
            // Get IPs (mock method 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)


也就是说,我建议将模拟方法分解为结构类型,而不是覆盖全局变量。这允许测试并行 运行,并且还允许你的包的下游用户编写他们自己的测试而不改变全局状态。

// 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)

海事组织。您可以将函数 net.Interface 和函数 getAddrs 注入 []net.AddrsGetIpAddress

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 测试码

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")
}

使用情况

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