在Go中测试连接的正确方法

I am covering project with tests and for that purpose I need dummy TCP Server, which could accept connection, write/read data to/from it, close it etc... I have found this question on stack overflow, covering mocking connection, but it doesn't cover what I actually need to test.

My idea relies on this article as starting point, but when I started implementing channel to let server write some data to newly opened connection, I got stuck with undebuggable deadlock in writing to channel.

What I want to achieve is to write some data to server's channel, say sendingQueue chan *[]byte, so later corresponding []byte will be sent to newly established connection.

During these little research I have tried debugging and printing out messages before/after sending data to channel and trying to send / read data from channel in different places of program.

What I found out:

  1. My idea works if I add data directly in handleConnection with

    go func() {
       f := []byte("test.")
       t.sendingQueue <- &f
    }()
    
  2. My idea doesn't work if I push data to channel from TestUtils_TestingTCPServer_WritesRequest in any form, either with func (t *TCPServer) Put(data *[]byte) (err error) or directly with:

    go func(queue chan *[]byte, data *[]byte) {
           queue <- data
    }(t.sendingQueue, &payload)
    
  3. It doesn't matter if channel is buffered or not.

So, obviously, there is something wrong either with the way I debug my code (I didn't dive into cli dlv, using just IDE debugger), or something that I completely miss about working with go channels, goroutines or net.Conn module.

For convenience public gist with full code is available. Note — there is // INIT part in the TestUtils_TestingTCPServer_WritesRequest which is required to run/debug single test. It should be commented out when running go test in the directory.

utils.go:


    // NewServer creates a new Server using given protocol
    // and addr.
    func NewTestingTCPServer(protocol, addr string) (*TCPServer, error) {
        switch strings.ToLower(protocol) {
        case "tcp":
            return &TCPServer{
                addr:         addr,
                sendingQueue: make(chan *[]byte, 10),
            }, nil
        case "udp":
        }
        return nil, errors.New("invalid protocol given")
    }

    // TCPServer holds the structure of our TCP
    // implementation.
    type TCPServer struct {
        addr         string
        server       net.Listener
        sendingQueue chan *[]byte
    }

    func (t *TCPServer) Run() (err error) {}
    func (t *TCPServer) Close() (err error) {}
    func (t *TCPServer) Put(data *[]byte) (err error) {}
    func (t *TCPServer) handleConnection(conn net.Conn){
        // <...>

        // Putting data here successfully sends it via freshly established
        // Connection:

        // go func() {
        //  f := []byte("test.")
        //  t.sendingQueue <- &f
        // }()
        for {
            fmt.Printf("Started for loop
")
            select {
            case data := <-readerChannel:
                fmt.Printf("Read written data
")
                writeBuffer.Write(*data)
                writeBuffer.Flush()
            case data := <-t.sendingQueue:
                fmt.Printf("Read pushed data
")
                writeBuffer.Write(*data)
                writeBuffer.Flush()
            case <-ticker:
                fmt.Printf("Tick
")
                return
            }
            fmt.Printf("Finished for loop
")
        }
    } 

utils_test.go


    func TestUtils_TestingTCPServer_WritesRequest(t *testing.T) {

        payload := []byte("hello world
")

        // <...> In gist here is placed INIT piece, which
        // is required to debug single test

        fmt.Printf("Putting payload into queue
")
        // This doesn't affect channel
        err = utilTestingSrv.Put(&payload)
        assert.Nil(t, err)

        // This doesn't work either
        //go func(queue chan *[]byte, data *[]byte) {
        //       queue <- data
        //}(utilTestingSrv.sendingQueue, &payload)

        conn, err := net.Dial("tcp", ":41123")
        if !assert.Nil(t, err) {
            t.Error("could not connect to server: ", err)
        }
        defer conn.Close()

        out := make([]byte, 1024)
        if _, err := conn.Read(out); assert.Nil(t, err) {
            // Need to remove trailing byte 0xa from bytes array to make sure bytes array are equal.
            if out[len(payload)] == 0xa {
                out[len(payload)] = 0x0
            }
            assert.Equal(t, payload, bytes.Trim(out, "\x00"))
        } else {
            t.Error("could not read from connection")
        }
    }

dongliang2058
dongliang2058 您的要点代码使用相同的端口在TestUtils_TestingTCPServer_WritesRequest()和init()中调用NewTestingTCPServer,这将导致工作不正常。
接近 2 年之前 回复
doujia7517
doujia7517 不幸的是,这无济于事,感谢您的尝试。我测试了有/没有goroutine和有/没有额外的功能f.e.在TestUtils_TestingTCPServer_WritesRequest中从客户端建立连接之前,仅向通道发送数据。
接近 2 年之前 回复
drbvm26000
drbvm26000 因为无法保证goroutine何时启动。没有数据竞争,但是有逻辑竞争。
接近 2 年之前 回复
doujiao4710
doujiao4710 我也这样认为,但事实并非如此。在goroutine中发送数据有什么问题?通道写入应该是goroutine安全的。
接近 2 年之前 回复
doubishi8303
doubishi8303 如果您使通道处于缓冲状态并且不在单独的goroutine中发送数据,则您的#2解决方案应该可以工作。在写入数据之前,可能还应该连接到服务器。
接近 2 年之前 回复

1个回答



在同事的帮助下并阅读了文章关于 init </ code>的工作方式,我发现了一个问题。</ p>

它在< code> init </ code>函数,由于使用:= </ code>分配,该函数正在重新创建额外的服务器。 我还更新了代码以确保服务器在 net.Dial </ code>和 conn.Read </ code>之前运行。</ p>
</ div>

展开原文

原文

After a help from a colleague and reading the article on how init works, I found a problem.

It was in init function, which was recreating extra server, due to using := assignment. I also updated code to make sure server runs before net.Dial and conn.Read.

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐