【问题标题】:How can you test code that relies on net.Conn without creating an actual network connection?如何在不创建实际网络连接的情况下测试依赖于 net.Conn 的代码?
【发布时间】:2016-11-02 15:51:53
【问题描述】:

如果我的代码可以与 net.Conn 一起使用,如何在不实际创建到 localhost 的网络连接的情况下为其编写测试?

我在网上没有看到任何解决方案;人们似乎要么忽略它(没有测试),要么编写无法并行运行的测试(即使用实际的网络连接,这会耗尽端口),或者使用 io.Pipe。

但是,net.Conn 定义了SetReadDeadlineSetWriteDeadline;而 io.Pipe 没有。 net.Pipe 没有,尽管表面上声称实现了接口,但它只是简单地实现了:

func (p *pipe) SetDeadline(t time.Time) error {
    return &OpError{Op: "set", Net: "pipe", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

func (p *pipe) SetReadDeadline(t time.Time) error {
    return &OpError{Op: "set", Net: "pipe", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

func (p *pipe) SetWriteDeadline(t time.Time) error {
    return &OpError{Op: "set", Net: "pipe", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

(见:https://golang.org/src/net/pipe.go

那么...还有其他方法可以做到这一点吗?

我将接受任何显示如何在测试中使用流的答案,其工作期限不是实际的网络套接字。

(懒惰地,这个cloudflare blogpost 涵盖了使用截止日期的动机,以及为什么在每个连接的 goroutine 中永远阻塞不是一个可接受的解决方案;但不管那个论点,特别是在这种情况下,我正在寻找一个解决方案 用于测试,我们故意要处理不良连接挂起等边缘情况。)

(注意。这可能看起来像 Simulate a tcp connection in Go 的副本,但请注意,该问题中提出的所有解决方案都没有实现 Deadline 功能,这正是我在这里询问如何测试的具体内容)

【问题讨论】:

  • 为什么不能使用网络连接?大多数需要 net.Conn 的测试都使用一个,通常是测试控制下的另一个本地端点。
  • 你要查看网卡的日志(以太网/wifi,...)。
  • 当你有很多很多测试时,每个测试使用一个实际的网络端口肯定不会扩展吗? (并行运行......)你做什么,选择一个随机端口并希望?分配一个端口池并乐观地绑定端口,直到找到一个空闲的端口?
  • 你让操作系统为你绑定端口。
  • 第 1 步:重构,您的代码仅使用 Conn 接口 (golang.org/pkg/net/#Conn) 第 2 步:实现 Conn 接口以进行测试 第 3 步:将实现中的值与预期值进行比较。

标签: unit-testing go


【解决方案1】:

对于单元测试中的受控版本,需要换出的代码应该存在于抽象之后。在这种情况下,抽象将是net.Conn 接口。生产代码将使用 go std lib net.Conn,但测试代码将使用配置有确切逻辑的测试存根来执行您的功能。

引入抽象是一种强大的模式,它应该允许在单元测试期间交换所有 IO 或基于时间的代码,以允许代码的受控执行。

@apxp 在评论中陈述了相同的方法。

同样的方法应该适用于最后期限。模拟达到的最后期限可能会变得很棘手,因为您可能必须为存根配置多个响应。 IE。第一个响应成功,但第二个响应模拟已达到的最后期限,并为第二个请求引发错误。

【讨论】:

  • 您能否更具体地说明截止日期?我看不出如何实际应用这个建议。我能看到的唯一可行的方法似乎是分叉网。以某种方式管道和实施假截止日期?但是如何实际做到这一点我不知道......
【解决方案2】:

您的问题非常开放,因此不可能给您“正确答案”。但我想我理解你卡住的地方。这个答案也是开放的,但它应该能让你回到正确的轨道上。

前几天我写了一个short article,说明了你必须使用的原理。

在我做一些小例子之前,我们需要解决一个重要的问题:

我们不测试网络包。我们假设 该软件包没有错误,并且按照文档中的说明进行操作。这意味着我们不关心 Go 团队如何实现 SetReadDeadlineSetWriteDeadline。我们只测试我们程序中的用法。

第 1 步:重构代码

你没有发布任何代码sn-ps,所以我只是给你一个简单的例子。我猜你有一个方法或函数,你正在使用 net 包。

func myConn(...) error {
  // You code is here
  c, err := net.Dial("tcp", "12.34.56.78:80")
  c.setDeadline(t)
  // More code here
}

你可以测试你需要重构你的函数,所以它只是使用 net.Conn 接口。为此,必须将 net.Dial() 调用移到函数之外。请记住,我们不想测试 net.Dial 功能。

新函数可能如下所示:

func myConn(c, net.Conn, ...) error {
  // You code is here
  c.setDeadline(t)
  // More code here
}

第 2 步:实现 net.Conn 接口

为了进行测试,您需要实现 net.Conn 接口:

type connTester struct {
    deadline time.Time
}

func (c *connTester) Read(b []byte) (n int, err error) {
    return 0, nil
}

...

func (c *connTester) SetDeadline(t time.Time) error {
    c.deadline = t
    return nil
}

...

完整的实现,包括一个小的类型检查: https://play.golang.org/p/taAmI61vVz

第 3 步:测试

测试的时候,我们不关心Dial()方法,我们只是创建一个指向我们的testtype的指针,它实现了net.Conn接口,并把它放到你的函数中。之后,我们查看测试用例,是否正确设置了截止日期参数。

func TestMyConn(t *testing.T){
  myconnTester = &connTester{}
  err := myConn(myconnTester,...)
  ...
  if myconntester.deadline != expectedDeadline{
  //Test fails
  }
}

因此,在测试时,您应该始终考虑要测试什么功能。我认为抽象你真正想要编写的功能是最困难的部分。在简单的单元测试中,您永远不应该测试标准库的功能。希望这些示例可以帮助您重回正轨。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 2011-11-21
    相关资源
    最近更新 更多