如何测试/重构测试调用http.ListenAndServe

时间:2019-04-16 01:52:52

标签: unit-testing go

我正在学习go并且正在开发一个简单的服务,该服务从队列中提取一些数据并将其粘贴到数据库中。它还运行一个Web服务器以允许抓取数据。现在我有两个go文件(为简洁起见,省略了一些文本):

func main() {
    parseConfig()

    s := &Service{ServiceConfig: config}
    err := s.Run()
    if err != nil {
        panic(err)
    }
}

然后是服务的定义(为简洁起见,省略了一些内容):

func (s *Service) Run() error {

    if err := s.validate(); err != nil {
        return err
    }

    if err := s.initDB(); err != nil {
        return err
    }
    defer s.db.Close()

    // Same pattern with health check library (init, start, close)
    // Same pattern starting queue consumer (init, start, close)

    s.mux = http.NewServeMux()
    s.registerHandlers(s.mux)

    http.ListenAndServe(":8080", s.mux)

    return nil
}

和结构

type Service struct {
    Config // Hold db connection info
    db  *sql.DB
    hc  *health
}

我能够很好地测试各个部分(例如initDBvalidate),但是由于http.ListenAndServe块,我不清楚如何测试Run函数。我最终超时了。以前,我将使用httpTest并创建一个测试服务器,但是那是main启动服务器的时候(应用程序起初比较基础)。

一些我要测试的东西:

启动后,我可以点击指标终点。 一旦启动,我就可以达到健康终点。 我可以将消息推送到队列中,并且一旦开始就可以接收它。 Run实际上开始时没有发生恐慌。

一些注意事项:我正在使用docker启动队列和数据库。测试Run函数的目的是确保引导程序可以正常工作,并且应用程序可以成功运行。最终,我将要通过队列推送数据并断言其已正确处理。

问题:我应该如何测试或重构它以便更容易地进行端到端的测试?

1 个答案:

答案 0 :(得分:1)

您可以使用goroutine构建测试工具以在单元测试中执行Run

func TestRun(t *testing.T) {
    service := Service{}
    serviceRunning := make(chan struct{})
    serviceDone := make(chan struct{})
    go func() {
        close(serviceRunning)
        err := service.Run()            
        defer close(serviceDone)
    }()

    // wait until the goroutine started to run (1)
    <-serviceRunning

    //
    // interact with your service to test whatever you want
    //

    // stop the service (2)
    service.Shutdown()

    // wait until the service is shutdown (3)
    <-serviceDone
}

这只是一个基本示例,以显示原则上该如何完成。在生产中应改进以下几点:

(0)最重要的细节:请勿在生产环境中使用http.ListenAndServe而是创建自己的http.Server实例。这样可以省去很多麻烦。

s.httpServer := http.Server {
    Addr: ":8080",
    Handler: s.mux,
}

(1)应该将服务正在运行的指示移到您的Service类型中。 Run中的初始化部分可能需要一段时间,并且指示通道应该在调用ListenAndServe之前关闭:

close(s.Running)
s.httpServer.ListenAndServe()

当然,您需要在服务类型中添加指示频道。

(2)向Shutdown添加一个Service方法,该方法调用s.httpServer.Shutdown()。这将导致对s.httpServer.ListenAndServe的调用返回并返回错误http.ErrServerClosed

if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    return err
}
return nil

在测试结束时关闭服务很重要。否则,您将不能对服务进行多个单元测试。无论如何,清理资源都是好公民。

(3)您需要等到service.Run返回以确保该服务实际上已关闭。