Golang中如何测试使用通道和信号实现的优雅HTTP服务器关闭

Golang中如何测试使用通道和信号实现的优雅HTTP服务器关闭 你好,

这是我的应用程序的主入口点,我想对其进行测试。然而,鉴于我接触Go语言才几天,我正在努力为其编写测试。从技术上讲,它会启动HTTP服务器,并在收到信号时优雅地关闭。请问我该如何测试这个功能?

谢谢

func main() {
	srv := http.NewServer()
	
	log.Print("app starting")

	shutChan := make(chan struct{})
	signChan := make(chan os.Signal, 1)
	
	signal.Notify(signChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
	
	ctx, cancel := context.WithCancel(context.Background())

	go listen(cancel, signChan)

	// This calls ListenAndServe behind the scene
	if err := srv.Start(ctx); err != nil {
		panic(err)
	}
	
	<-shutChan

	log.Print("app shutdown")
}

func listen(cancel context.CancelFunc, signChan chan os.Signal) {
	sig := <-signChan

	log.Printf("%v signal received", sig)

	cancel()
}

更多关于Golang中如何测试使用通道和信号实现的优雅HTTP服务器关闭的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

我会照做的。谢谢。

更多关于Golang中如何测试使用通道和信号实现的优雅HTTP服务器关闭的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


要测试 main 函数中的某些内容,你必须将 main 中的代码放入另一个函数中,然后你就可以从测试函数中调用该函数。

将以下代码:

func main() {
    do things ...
}

修改为:

func main() {
    foo()
}

func foo() {
    do things ...
}

func TestFoo(t *testing.T) {
    foo()
    if state != expected {
        t.Error("unexpected state")
    }
}

您是指使用 go test 调用的单元测试进行测试吗?这只有在能够通过代码生成 SIGINT 或 SIGTERM 信号时才可能实现。我不确定这是否可行(signChan<-)。此外,无法使用 go test 测试 main 函数。要做到这一点,您必须将代码封装到可以测试的函数中。

出于这些原因,我会通过运行代码并输入 ctrl-C 来手动测试这段代码。

顺便提一下,我在 http 文档或源代码中找不到 Start() 方法。通常,您应该在监听函数中调用 srv.Shutdown(ctx),并且给定的上下文通常是超时上下文,因为 Shutdown 会阻塞直到所有连接都返回空闲状态。这并非不可能永远阻塞。

还要注意,os.Interrupt 等于 syscall.SIGINT(定义在 os/exec_posix.go 中)。

感谢您的建议。

是的,我的意思是我希望用 go test 命令进行测试。目前我正按照您的建议进行手动测试,整个过程运行良好。由于我还在学习阶段,所以不知道如何在单元测试中模拟手动测试。

Start() 函数实际上触发了 ListenAndServe,这个函数是代码最开头 http.NewServer() 的一部分,因此它是一个包装器。您可以在这里看到它,这是您之前回答过的问题。

不过,我仍然需要以某种方式测试 cmd/api/main.go 中的内容。如果您想知道我的应用程序是什么样子,请看下面。我是一名学习者,所以欢迎任何改进建议。

cmd/api/main.go

package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/joho/godotenv"
	"myapp/internal/app"
	"myapp/internal/http"
)

func main() {
	srv := http.NewServer()

	log.Print("app starting")

	signChan := make(chan os.Signal, 1)

	signal.Notify(signChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

	ctx, cancel := context.WithCancel(context.Background())

	go listen(cancel, signChan)

	if err := app.New(srv).Start(ctx); err != nil {
		panic(err)
	}

	log.Print("app shutdown")
}

func listen(cancel context.CancelFunc, signChan chan os.Signal) {
	sig := <-signChan

	log.Printf("%v signal received", sig)

	cancel()
}

internal/app/api.go

package app

import (
	"context"
	"fmt"
	"log"
	"time"

	"myapp/internal/http"
)

type App struct {
	srv http.Server
}

func New(srv http.Server) App {
	return App{srv: srv}
}

func (a App) Start(ctx context.Context) error {
	shutChan := make(chan struct{})

	go shutdown(ctx, shutChan, a)

	if err := a.srv.Start(); err != nil {
		return fmt.Error("http: failed to start")
	}

	<-shutChan

	return nil
}

func shutdown(ctx context.Context, shutChan chan<- struct{}, a App) {
	<-ctx.Done()

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	err := a.srv.Shutdown(ctx)
	if err != nil {
		log.Print("http: interrupted some active connections")
	} else {
		log.Print("http: served all active connections")
	}

	close(shutChan)
}

internal/http/server.go

package http

import (
	"net/http"
)

type Server struct {
	*http.Server
}

func NewServer(adr string) Server {
	return Server{
		&http.Server{Addr: adr},
	}
}

func (s Server) Start() error {
	if err := s.ListenAndServe(); err != http.ErrServerClosed {
		return err
	}

	return nil
}
回到顶部