在Go中模拟TCP连接

9
在Go语言中,TCP连接(net.Conn)是一个io.ReadWriteCloser。我想通过模拟TCP连接来测试我的网络代码。我有两个要求:
  1. 需要读取的数据存储在字符串中
  2. 每当写入数据时,我希望它被存储在某种缓冲区中,以便稍后访问
是否有一种数据结构或者简单的方法可以实现这个需求?
4个回答

6

不知道在问题被提出时是否已经存在了这个功能,但是你可能需要使用 net.Pipe() 函数,它可以为你提供两个全双工的 net.Conn 实例,它们相互连接。


5

编辑: 我将这个答案整合到一个包中,使事情变得更加简单 - 请参见此处: https://github.com/jordwest/mock-conn


虽然Ivan的解决方案对于简单的情况是可行的,但请记住,真正的TCP连接实际上是两个缓冲区,或者说是管道。例如:

 Server   |   Client
 ---------+---------
  reads <===  writes
 writes ===>  reads

如果您使用一个单一的缓冲区,服务器既可以从中读取数据,也可以向其中写入数据,这可能导致服务器与自身通信。
以下是一种解决方案,它允许您将MockConn类型作为ReadWriteCloser传递给服务器。Read、Write和Close函数只是代理到管道末端的服务器上的函数。
type MockConn struct {
    ServerReader *io.PipeReader
    ServerWriter *io.PipeWriter

    ClientReader *io.PipeReader
    ClientWriter *io.PipeWriter
}

func (c MockConn) Close() error {
    if err := c.ServerWriter.Close(); err != nil {
        return err
    }
    if err := c.ServerReader.Close(); err != nil {
        return err
    }
    return nil
}

func (c MockConn) Read(data []byte) (n int, err error)  { return c.ServerReader.Read(data) }
func (c MockConn) Write(data []byte) (n int, err error) { return c.ServerWriter.Write(data) }

func NewMockConn() MockConn {
    serverRead, clientWrite := io.Pipe()
    clientRead, serverWrite := io.Pipe()

    return MockConn{
        ServerReader: serverRead,
        ServerWriter: serverWrite,
        ClientReader: clientRead,
        ClientWriter: clientWrite,
    }
}

当模拟“服务器”连接时,只需在使用net.Conn的位置上使用MockConn(这显然仅实现了ReadWriteCloser接口,如果需要支持完整的net.Conn接口,您可以轻松添加虚拟方法,如LocalAddr()等)。
在您的测试中,您可以通过读取和写入ClientReaderClientWriter字段来扮演客户端的角色。
func TestTalkToServer(t *testing.T) {
    /*
     * Assumes that NewMockConn has already been called and
     * the server is waiting for incoming data
     */

    // Send a message to the server
    fmt.Fprintf(mockConn.ClientWriter, "Hello from client!\n")

    // Wait for the response from the server
    rd := bufio.NewReader(mockConn.ClientReader)
    line, err := rd.ReadString('\n')

    if line != "Hello from server!" {
        t.Errorf("Server response not as expected: %s\n", line)
    }
}

我有点困惑。你在这里发布的代码似乎与你放在Github上的不符。 - Jay Sullivan

4

为什么不使用 bytes.Buffer 呢? 它是一个 io.ReadWriter 并且有一个 String 方法来获取存储的数据。如果你需要将其定义为 io.ReadWriteCloser,可以定义自己的类型:

type CloseableBuffer struct {
    bytes.Buffer
}

并定义一个Close方法:

func (b *CloseableBuffer) Close() error {
    return nil
}

0
在大多数情况下,您不需要模拟 net.Conn。
您只需要模拟那些会增加测试时间、防止测试并行运行(使用共享资源,如硬编码文件名)或可能导致故障的东西(您可能会耗尽连接限制或端口,但在大多数情况下这不是一个问题,当您在隔离环境中运行测试时)。
不模拟的优点是更精确地测试您想要测试的实际内容。

https://www.accenture.com/us-en/blogs/software-engineering-blog/to-mock-or-not-to-mock-is-that-even-a-question

不要模拟 net.Conn,您可以编写一个 mock 服务器,在测试中使用 goroutine 运行它,并使用真正的 net.Conn 连接到它。

一个快速而简单的例子:

port := someRandomPort()
srv := &http.Server{Addr: port}
go func(msg string) {
    http.HandleFunc("/hello", myHandleFUnc)
    srv.ListenAndServe()
}
myTestCodeUsingConn(port)
srv.Shutdown(context.TODO())

一个代码示例可以帮助OP更好地理解它如何工作。 - jnovack
一个例子和更详细的解释已经添加。 - dparnovskiy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接