Golang中如何测试HTTP ListenAndServe的包装函数

Golang中如何测试HTTP ListenAndServe的包装函数 你好,

我在想如何测试下面的 Start 方法,或者我是否真的应该测试它?如果我在测试中直接调用 Start,它会挂起,因为我认为它实际上启动了服务器,而这正是我们想要避免的。

谢谢

package http

import (
	"net/http"
)

type Server struct {
	*http.Server
}

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

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

	return nil
}
func TestServer_Start(t *testing.T) {
	// ???
}

更多关于Golang中如何测试HTTP ListenAndServe的包装函数的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

非常感谢 @Christophe_Meessen。这对我的学习帮助很大。

更多关于Golang中如何测试HTTP ListenAndServe的包装函数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


测试函数可以启动一个Go协程,在休眠1秒后调用Shutdown。这样你就可以在测试中调用Start函数。但是你真的需要测试如此简单的函数吗?

以下是一个示例代码,它将在1秒后调用Shutdown并停止服务器。

func TestServer_Start(t *testing.T) {
	srv := NewServer(":45566")
	go func () {
		time.Sleep(1*time.Second)
		srv.Shutdown(context.Background())
	}()
	err := srv.Start()
	if err != nil {
		t.Error("unexpected error:", err)
	}
}

要测试 Start 方法,可以通过启动服务器后立即关闭它来避免测试挂起。这里是一个示例测试:

package http

import (
	"context"
	"net/http"
	"testing"
	"time"
)

func TestServer_Start(t *testing.T) {
	server := NewServer(":0") // 使用 :0 让系统分配随机端口

	// 在 goroutine 中启动服务器
	go func() {
		if err := server.Start(); err != nil && err != http.ErrServerClosed {
			t.Errorf("Start failed: %v", err)
		}
	}()

	// 给服务器一点时间启动
	time.Sleep(100 * time.Millisecond)

	// 优雅关闭服务器
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	
	if err := server.Shutdown(ctx); err != nil {
		t.Errorf("Shutdown failed: %v", err)
	}
}

如果需要测试实际的 HTTP 请求处理,可以这样写:

func TestServer_StartWithHandler(t *testing.T) {
	// 创建自定义 handler 的服务器
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})
	
	server := &http.Server{
		Addr:    ":0",
		Handler: handler,
	}
	
	wrappedServer := Server{server}

	// 获取实际监听的地址
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatal(err)
	}
	server.Addr = listener.Addr().String()

	// 启动服务器
	go func() {
		if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
			t.Errorf("Serve failed: %v", err)
		}
	}()

	// 给服务器启动时间
	time.Sleep(100 * time.Millisecond)

	// 测试 HTTP 请求
	resp, err := http.Get("http://" + server.Addr + "/")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("Expected status 200, got %d", resp.StatusCode)
	}

	// 关闭服务器
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	server.Shutdown(ctx)
}

对于 Start 方法中错误处理的测试:

func TestServer_Start_Error(t *testing.T) {
	// 创建已经在使用的端口
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatal(err)
	}
	defer listener.Close()

	// 尝试在相同端口启动服务器
	server := NewServer(listener.Addr().String())
	
	// 这个应该快速失败
	errCh := make(chan error, 1)
	go func() {
		errCh <- server.Start()
	}()

	select {
	case err := <-errCh:
		if err == nil {
			t.Error("Expected error when starting server on used port")
		}
	case <-time.After(1 * time.Second):
		t.Error("Start should have failed immediately")
	}
}

这些测试方法可以验证 Start 方法的基本功能,同时避免测试无限挂起。

回到顶部