如果我的代码适用于net.Conn
,如何在不实际创建与localhost的网络连接的情况下为其编写测试?
我在网上看不到任何解决方案;人们似乎要么忽略它(没有测试),编写不能并行运行的测试(即使用实际的网络连接,使用up端口),或者使用io.Pipe。
但是,net.Conn
定义SetReadDeadline
,SetWriteDeadline
;和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中永久阻止不是一个可接受的解决方案;但不管这个论点,特别是在这种情况下,我正在寻找一个解决方案 for tests 我们故意要处理坏连接挂起的边缘情况等。)
(注意,这可能看起来像是Simulate a tcp connection in Go的副本,但请注意,该问题中提出的所有解决方案都没有实现截止日期功能,具体是我在这里要求如何测试的问题)
答案 0 :(得分:5)
您的问题非常开放,因此无法向您提供“正确答案”。但我想我明白你坚持的地步。这个答案也是公开的,但它应该会让你重回正轨。
前几天我写了short article,其中显示了你必须使用的原则。
在我做一些小例子之前,我们需要解决一个重点:
我们不测试网络包。我们认为,包没有错误并且确实如此,文档说的是什么。这意味着我们并不关心Go团队如何实施 SetReadDeadline
和SetWriteDeadline
。我们只测试程序中的用法。
第1步:重构代码
你没有发布任何代码片段,所以我给你一个简单的例子。我猜你有一个方法或功能,你正在使用网络包。
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
}
}
因此,在测试时,您应该始终考虑要测试的功能。我认为抽象你真正想写的功能是最困难的部分。在简单的单元测试中,您永远不应该测试标准库的功能。希望这些例子可以帮助您重回正轨。
答案 1 :(得分:2)
对于单元测试中的受控版本,需要换出的代码应该落后于抽象。在这种情况下,抽象将是net.Conn
接口。生产代码将使用go std lib net.Conn
,但测试代码将使用配置了完整逻辑的测试存根来运行您的功能。
引入抽象是一种强大的模式,应该允许交换所有IO或基于时序的代码,以允许在单元测试期间控制代码的执行。
@apxp在评论中说明了相同的方法。
同样的方法应该适用于截止日期。它可能会很难模拟到达的截止日期,因为您可能必须使用多个响应配置存根。即。第一个响应成功,但第二个响应模拟已达到的截止日期,并为第二个请求引发错误。