Golang中如何显式返回错误以测试http.Shutdown函数

Golang中如何显式返回错误以测试http.Shutdown函数 你好,

我测试了“正常”的测试用例,但想知道如何测试“异常”的测试用例。尝试传递一个已过期的上下文但没有成功。

谢谢

异常

func TestServer_Stop(t *testing.T) {
	srv := New(":1234")

	ctx, _ := context.WithTimeout(context.Background(), 1 * time.Millisecond)
	time.Sleep(5 * time.Millisecond)

	assert.Equal(t, "bad", srv.Stop(ctx)) // 这里返回的是 good
}

代码

func (s Server) Stop(ctx context.Context) string {
	if e := s.Shutdown(ctx); e != nil {
		return "bad"
	} else {
		return "good"
	}
}

正常 这个没问题

func TestServer_Stop(t *testing.T) {
	srv := New(":1234")

	assert.Equal(t, "good", srv.Stop(context.Background()))
}

更多关于Golang中如何显式返回错误以测试http.Shutdown函数的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

显然,Shutdown 成功是因为服务器关闭成功了。 查看下面的 Shutdown 代码,我们发现它只有在无法关闭所有连接时才会检查 ctx 错误。

为了让 Shutdown 返回错误,你需要有一个非空闲的连接。我建议你尝试执行一个 GET 操作但不读取数据。这可能会留下一个非空闲的连接。如果这还不够,可以尝试增加服务器返回的数据量,以防数据在客户端被缓冲。

此外,你可以创建一个带有截止时间(deadline)的上下文,并将时间设置为过去。这样可以避免需要休眠。

func (srv *Server) Shutdown(ctx context.Context) error {
	atomic.StoreInt32(&srv.inShutdown, 1)

	srv.mu.Lock()
	lnerr := srv.closeListenersLocked()
	srv.closeDoneChanLocked()
	for _, f := range srv.onShutdown {
		go f()
	}
	srv.mu.Unlock()

	ticker := time.NewTicker(shutdownPollInterval)
	defer ticker.Stop()
	for {
		if srv.closeIdleConns() {
			return lnerr
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
		}
	}
}

更多关于Golang中如何显式返回错误以测试http.Shutdown函数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


要显式返回错误以测试http.Shutdown函数,可以使用自定义的http.Server并模拟Shutdown行为。以下是两种方法:

方法1:使用接口和模拟实现

type server interface {
    Shutdown(ctx context.Context) error
}

type realServer struct {
    *http.Server
}

func (s *realServer) Shutdown(ctx context.Context) error {
    return s.Server.Shutdown(ctx)
}

type mockServer struct {
    shutdownErr error
}

func (m *mockServer) Shutdown(ctx context.Context) error {
    return m.shutdownErr
}

func (s Server) Stop(ctx context.Context, server server) string {
    if e := server.Shutdown(ctx); e != nil {
        return "bad"
    }
    return "good"
}

func TestServer_Stop_Error(t *testing.T) {
    srv := New(":1234")
    mock := &mockServer{shutdownErr: errors.New("shutdown error")}
    
    result := srv.Stop(context.Background(), mock)
    assert.Equal(t, "bad", result)
}

方法2:直接注入错误(更直接)

func TestServer_Stop_WithError(t *testing.T) {
    srv := New(":1234")
    
    // 创建立即取消的上下文
    ctx, cancel := context.WithCancel(context.Background())
    cancel()
    
    // 这会强制Shutdown返回上下文错误
    result := srv.Stop(ctx)
    assert.Equal(t, "bad", result)
}

方法3:使用nettest包模拟网络错误

import "golang.org/x/net/nettest"

func TestServer_Stop_NetworkError(t *testing.T) {
    srv := New(":1234")
    
    // 创建会超时的上下文
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
    defer cancel()
    
    // 确保上下文立即过期
    time.Sleep(1 * time.Millisecond)
    
    result := srv.Stop(ctx)
    assert.Equal(t, "bad", result)
}

方法4:修改Server结构以支持测试注入

type Server struct {
    server *http.Server
    shutdownFunc func(ctx context.Context) error
}

func (s Server) Stop(ctx context.Context) string {
    shutdown := s.shutdownFunc
    if shutdown == nil {
        shutdown = s.server.Shutdown
    }
    
    if e := shutdown(ctx); e != nil {
        return "bad"
    }
    return "good"
}

func TestServer_Stop_WithMockedError(t *testing.T) {
    srv := New(":1234")
    srv.shutdownFunc = func(ctx context.Context) error {
        return errors.New("simulated shutdown error")
    }
    
    result := srv.Stop(context.Background())
    assert.Equal(t, "bad", result)
}

对于你的具体测试用例,问题在于http.Shutdown在上下文过期时可能不会立即返回错误。使用context.WithCancel并立即取消会更可靠:

func TestServer_Stop_Timeout(t *testing.T) {
    srv := New(":1234")
    
    ctx, cancel := context.WithCancel(context.Background())
    cancel() // 立即取消上下文
    
    // 给Shutdown一点时间处理
    time.Sleep(10 * time.Millisecond)
    
    assert.Equal(t, "bad", srv.Stop(ctx))
}
回到顶部