单元测试-httptest

项目demo地址:go-test

本文主要针对单元测试工具,其他工具请看专栏内其它博客。

httptest

**介绍:**Go 内置标准库net/http/httptest,核心用途用于测试net/http构建的HTTP服务(如API接口、Web服务等),它可以模拟HTTP请求发送和HTTP响应的接收,无需启动真实的HTTP服务器即可完成接口测试,极大提升了测试的便捷性和执行效率

优点:

  • 无需启动真实服务器:无需调用 http.ListenAndServe 启动端口监听,直接测试 HTTP 处理器(Handler/HandlerFunc),测试执行更高效。
  • 脱离网络依赖:模拟 HTTP 请求与响应的完整生命周期,不受网络波动、端口占用等外部因素影响,测试结果稳定可复现。
  • 精准捕获响应细节:可完整获取响应状态码、响应头、响应体等所有信息,便于精准断言验证。
  • 支持两种核心测试场景:
    • 测试 HTTP 处理器(直接调用 ServeHTTP,最常用)
    • 启动临时测试服务器(模拟真实服务端,用于客户端测试或集成测试)

1.安装

内置工具可以直接使用

2.使用示例

相关代码在gitee代码仓库的示例代码中,仓库地址请看博客开头

blog.go

go 复制代码
package httptest_demo

import (
	"errors"
	"fmt"
	"io"
	"net/http"
)

func SearchHttp(targetURL string) (interface{}, error) {
	resp, err := http.Get(targetURL)
	if err != nil {
		errMsg := fmt.Sprintf("发送 GET 请求失败:%v", err)
		fmt.Println(errMsg)
		return nil, errors.New(errMsg)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		errMsg := fmt.Sprintf("请求失败,状态码:%d,状态信息:%s", resp.StatusCode, resp.Status)
		fmt.Println(errMsg)
		return nil, errors.New(errMsg)
	}

	bodyBytes, _ := io.ReadAll(resp.Body)
	return string(bodyBytes), nil
}

blog_test.go

go 复制代码
package httptest_demo

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestSearchHttp(t *testing.T) {
	// --------------- 1. 定义所有测试场景的表驱动用例(循环遍历执行) ---------------
	testCases := []struct {
		name             string        // 用例名称
		prepareFunc      func() string // 前置准备:创建模拟服务器/构造URL,返回待请求的URL
		expectedErr      bool          // 是否预期返回错误
		errContains      string        // 预期错误信息包含的关键字(非空则验证)
		expectedNonEmpty bool          // 正常场景下,是否预期返回非空字符串
	}{
		// 场景1:正常请求(200 OK,响应体正常)
		{
			name: "正常请求-状态码200",
			prepareFunc: func() string {
				// 启动模拟HTTP服务器,返回200和测试响应体
				mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					mockBody := "<!DOCTYPE html><html><title>百度一下</title></html>"
					w.WriteHeader(http.StatusOK)
					_, _ = w.Write([]byte(mockBody))
				}))
				// 关键:将mockServer放入测试上下文,确保后续能关闭(避免资源泄露)
				t.Cleanup(func() { mockServer.Close() })
				return mockServer.URL
			},
			expectedErr:      false,
			errContains:      "",
			expectedNonEmpty: true,
		},
		// 场景2:请求失败(无效URL,模拟网络异常)
		{
			name: "异常场景-无效URL请求失败",
			prepareFunc: func() string {
				// 返回一个无效的URL,触发http.Get请求失败
				return "http://invalid-xxx-url-12345/"
			},
			expectedErr:      true,
			errContains:      "发送 GET 请求失败",
			expectedNonEmpty: false,
		},
		// 场景3:状态码非200(模拟404 Not Found)
		{
			name: "异常场景-状态码404",
			prepareFunc: func() string {
				// 启动模拟HTTP服务器,返回404状态码
				mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte("页面不存在"))
				}))
				t.Cleanup(func() { mockServer.Close() })
				return mockServer.URL
			},
			expectedErr:      true,
			errContains:      "请求失败,状态码:404",
			expectedNonEmpty: false,
		},
		// 场景4:状态码非200(模拟500服务器内部错误)
		{
			name: "异常场景-状态码500",
			prepareFunc: func() string {
				// 启动模拟HTTP服务器,返回500状态码
				mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					w.WriteHeader(http.StatusInternalServerError)
					_, _ = w.Write([]byte("服务器内部错误"))
				}))
				t.Cleanup(func() { mockServer.Close() })
				return mockServer.URL
			},
			expectedErr:      true,
			errContains:      "请求失败,状态码:500",
			expectedNonEmpty: false,
		},
	}

	// --------------- 2. 循环遍历所有测试用例,统一执行验证 ---------------
	for _, tc := range testCases {
		// 循环内使用t.Run创建子用例(便于精准定位失败场景,不影响其他用例)
		t.Run(tc.name, func(t *testing.T) {
			// 步骤1:执行前置准备,获取待请求的URL
			targetURL := tc.prepareFunc()

			// 步骤2:调用被测函数
			result, err := SearchHttp(targetURL)

			// 步骤3:统一断言验证
			// 3.1 验证错误是否符合预期
			if (err != nil) != tc.expectedErr {
				t.Fatalf("错误预期不符:预期是否错误[%t],实际是否错误[%t],错误信息[%v]",
					tc.expectedErr, err != nil, err)
			}

			// 3.2 若预期错误,验证错误信息是否包含指定关键字
			if tc.expectedErr && tc.errContains != "" {
				if !strings.Contains(err.Error(), tc.errContains) {
					t.Errorf("错误信息不符:预期包含[%s],实际错误[%v]", tc.errContains, err)
				}
			}

			// 3.3 验证返回值是否符合预期
			if tc.expectedErr {
				// 异常场景:预期返回nil
				if result != nil {
					t.Errorf("异常场景预期返回nil,实际返回[%v],类型[%T]", result, result)
				}
			} else {
				// 正常场景:验证返回值是string类型,且非空(若预期非空)
				resultStr, ok := result.(string)
				if !ok {
					t.Fatalf("正常场景预期返回string类型,实际返回[%T]", result)
				}
				if tc.expectedNonEmpty && len(resultStr) == 0 {
					t.Error("正常场景预期返回非空字符串,实际返回空字符串")
				}
			}
		})
	}
}

命令行执行命令

go test -cover

结果:

powershell 复制代码
PS D:\wyl\workspace\go\tracer\logic\httptest_demo> go test -cover         
发送 GET 请求失败:Get "http://invalid-xxx-url-12345/": dial tcp: lookup invalid-xxx-url-12345: no such host
请求失败,状态码:404,状态信息:404 Not Found
请求失败,状态码:500,状态信息:500 Internal Server Error
PASS
coverage: 100.0% of statements
ok      tracer/logic/httptest_demo      1.683s
相关推荐
HashFlag4 小时前
单元测试-go-sqlmock
golang·单元测试·sqlmock
139的世界真奇妙4 小时前
工作事宜思考点
经验分享·笔记·golang·go
Grassto15 小时前
16 Go Module 常见问题汇总:依赖冲突、版本不生效的原因
golang·go·go module
流浪克拉玛依18 小时前
从超卖到原子性:Redis Lua 解决秒杀库存扣减实战
go
薯条不要番茄酱21 小时前
【测试实战篇】“发好论坛”接口自动化测试
python·功能测试·测试工具·单元测试·测试用例·pytest·测试覆盖率
怕浪猫2 天前
第16章:标准库精讲(二)net/http、json、time
后端·go·编程语言
下次一定x2 天前
深度解析Kratos服务注册:从框架入口到Consul落地实现
后端·go
Sandy_Star2 天前
1.5 行政强制和税收保障措施
单元测试