如何对修改发送消息的 net.Conn 函数进行单元测试?

Posted

技术标签:

【中文标题】如何对修改发送消息的 net.Conn 函数进行单元测试?【英文标题】:How to unit test a net.Conn function that modifies the message sent? 【发布时间】:2021-12-10 00:43:09 【问题描述】:

首先,让我告诉您,我还看到了与此类似的其他问题,但我认为没有任何问题能够真正详细地回答它(How does one test net.Conn in unit tests in Golang? 和 How does one test net.Conn in unit tests in Golang?)。

我要测试的是以某种方式响应 TCP 请求的函数。

在最简单的情况下:

func HandleMessage(c net.Conn) 
  s, err := bufio.NewReader(c).ReadString('\n')
  s = strings.Trim(s, "\n")

  c.Write([]byte(s + " whatever"))

我将如何对这样的功能进行单元测试,理想情况下使用net.Pipe() 来不打开实际连接。我一直在尝试这样的事情:

func TestHandleMessage(t *testing.T) 
  server, client := net.Pipe()

  go func()
    server.Write([]byte("test"))
    server.Close()
  ()

  s, err := bufio.NewReader(c).ReadString('\n')

  assert.Nil(t, err)
  assert.Equal(t, "test whatever", s) 

但是我似乎无法理解将 HandleMessage(client) (或 HandleMessage(server)? 放在测试中以实际获得我想要的响应的位置。我可以让它要么阻塞并且不会完成完全没有,否则它将返回与我在服务器中编写的完全相同的字符串。

有人可以帮我指出我在哪里犯了错误吗?或者在测试 TCP 功能时指出正确的方向。

【问题讨论】:

您指的是您的代码无法重现的功能。所以我只能猜测我的答案。您应该提供完整的、最小的、可验证的行为示例,这样您就不会让我们猜测。 【参考方案1】:

net.Pipedocs say:

Pipe 创建一个同步的、内存中的、全双工的网络连接;两端都实现了 Conn 接口。一端的读取与另一端的写入匹配,直接在两者之间复制数据;没有内部缓冲。

因此,您附加到net.Conn 的标签(serverclient)是任意的。如果您发现它更容易理解,请随意使用 handleMessageConn, sendMessageConn := net.Pipe() 行。

下面基本填写了answer you mentioned中给出的例子。

func TestHandleMessage(t *testing.T) 
    server, client := net.Pipe()

    // Set deadline so test can detect if HandleMessage does not return
    client.SetDeadline(time.Now().Add(time.Second))

    // Configure a go routine to act as the server
    go func() 
        HandleMessage(server)
        server.Close()
    ()


    _, err := client.Write([]byte("test\n"))
    if err != nil 
        t.Fatalf("failed to write: %s", err)
    

    // As the go routine closes the connection ReadAll is a simple way to get the response
    in, err := io.ReadAll(client)
    if err != nil 
        t.Fatalf("failed to read: %s", err)
    

    // Using an Assert here will also work (if using a library that provides that functionality)
    if string(in) != "test whatever" 
        t.Fatalf("expected `test` got `%s`", in)
    

    client.Close()

您可以扭转这种局面并将Write/Read 放在go 例程中,但我相信上述方法更容易理解并简化了避免a limitation 的测试包:

当测试函数返回或调用任何方法 FailNow、Fatal、Fatalf、SkipNow、Skip 或 Skipf 时,测试结束。这些方法以及 Parallel 方法只能从运行 Test 函数的 goroutine 调用

注意:如果您不需要net.Conn(就像这个简单示例中的情况),请考虑使用HandleMessage(c io.ReadWriter)(这为用户提供了更大的灵活性并简化了测试)。

【讨论】:

HandleMessage 使用 io.ReadWriter 的建议似乎不错。我能够构建一个 PofC,其中HandleMessage(io.ReadWriter)net.Conn(我使用net.Pipe())和实现io.ReadWriterbytes.Buffer 一起使用。 哦!这正是我想要的,而我一直觉得自己在逃避。确实,更改名称以便更容易推理并在 go func 之外编写使一切变得不同。感谢您提供更具体的示例。【参考方案2】:

阻塞并且根本不会完成

好吧,您发布的代码是如此不完整,以至于我无法知道这些是否是您面临的真正问题-例如,TestHandleMessage 中没有c。但是给你怀疑的好处,我会说这段代码有两个问题:

首先,您的测试从不写入client,因此server 没有任何内容可读取。你写信给server 并关闭它,但你从来没有写任何东西给client。所以这是第一个问题,但同样,该代码无法编译。

其次,看这个组合:

  c.Write([]byte(s + " whatever"))
  s, err := bufio.NewReader(c).ReadString('\n')

HandleMessage 不写换行符,但客户端需要一个。它无限期地挂起,等待永远不会被写入的换行符。同样,我不确定您是否遇到了 this 问题或第一个问题或两者兼有 - 因为您的代码无法编译。

你必须改变这一行:

  c.Write([]byte(s + " whatever\n"))

您还必须将初始字符串写入 client 连接,以便管道另一端的server 可以读取它。

将所有内容放在一起,提取任何外部依赖项(作证),并修复一些错误最终得到:

t.go:

package main

import (
    "net"
    "fmt"
    "bufio"
    "strings"
)

func HandleMessage(c net.Conn) 
  s, err := bufio.NewReader(c).ReadString('\n')
    if err != nil 
        panic(fmt.Errorf("HandleMessage could not read: %w", err))
    
  s = strings.Trim(s, "\n")
  c.Write([]byte(s + " whatever\n"))

t_test.go:

package main

import(
    "net"
    "bufio"
    "fmt"
    "testing"
)

func TestHandleMessage(t *testing.T) 
  server, client := net.Pipe()

  go func()
    HandleMessage(server)
  ()
    fmt.Fprintln(client, "test")
  s, err := bufio.NewReader(client).ReadString('\n')

  if err != nil 
        t.Errorf("Error should be nil, got: %s", err.Error())
    
    if s != "test whatever\n" 
        t.Errorf("Expected result to be 'test whatever\\n', got %s", s)
    

% go test t.go t_test.go -v
=== RUN   TestHandleMessage
--- PASS: TestHandleMessage (0.00s)
PASS
ok      command-line-arguments  0.100s

【讨论】:

以上是关于如何对修改发送消息的 net.Conn 函数进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

呼不入呼不出掉话单通双不通问题分析方法

呼不入呼不出掉话单通双不通问题分析方法

如何在 Visual Studio 代码中创建和部署函数应用程序?

我如何对 Slackbot 进行编程以每周自动发送常规消息

关于优秀编码的思考

2016011998 张舒凯 散列函数的应用及其安全性