在Go语言中,TCP连接(net.Conn)是一个io.ReadWriteCloser。我想通过模拟TCP连接来测试我的网络代码。我有两个要求:
- 需要读取的数据存储在字符串中
- 每当写入数据时,我希望它被存储在某种缓冲区中,以便稍后访问
不知道在问题被提出时是否已经存在了这个功能,但是你可能需要使用 net.Pipe()
函数,它可以为你提供两个全双工的 net.Conn
实例,它们相互连接。
编辑: 我将这个答案整合到一个包中,使事情变得更加简单 - 请参见此处: https://github.com/jordwest/mock-conn
虽然Ivan的解决方案对于简单的情况是可行的,但请记住,真正的TCP连接实际上是两个缓冲区,或者说是管道。例如:
Server | Client
---------+---------
reads <=== writes
writes ===> reads
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()
等)。ClientReader
和ClientWriter
字段来扮演客户端的角色。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)
}
}
为什么不使用 bytes.Buffer
呢? 它是一个 io.ReadWriter
并且有一个 String
方法来获取存储的数据。如果你需要将其定义为 io.ReadWriteCloser
,可以定义自己的类型:
type CloseableBuffer struct {
bytes.Buffer
}
并定义一个Close
方法:
func (b *CloseableBuffer) Close() error {
return nil
}
不要模拟 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())