Go 单元测试之HTTP请求与API测试

文章目录

    • 一、httptest
      • 1.1 前置代码准备
      • 1.2 介绍
      • 1.3 基本用法
    • 二、gock
      • 2.1介绍
      • 2.2 安装
      • 2.3 基本使用
      • 2.4 举个例子
        • 2.4.1 前置代码
        • 2.4.2 测试用例

一、httptest

1.1 前置代码准备

假设我们的业务逻辑是搭建一个http server端,对外提供HTTP服务。用来处理用户登录请求,用户需要输入邮箱,密码。

package main

import (
	regexp "github.com/dlclark/regexp2"
	"github.com/gin-gonic/gin"
	"net/http"
)

type UserHandler struct {
	emailExp    *regexp.Regexp
	passwordExp *regexp.Regexp
}

func (u *UserHandler) RegisterRoutes(server *gin.Engine) {
	ug := server.Group("/user")
	ug.POST("/login", u.Login)
}
func NewUserHandler() *UserHandler {
	const (
		emailRegexPattern    = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
		passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`
	)
	emailExp := regexp.MustCompile(emailRegexPattern, regexp.None)
	passwordExp := regexp.MustCompile(passwordRegexPattern, regexp.None)
	return &UserHandler{
		emailExp:    emailExp,
		passwordExp: passwordExp,
	}
}

type LoginRequest struct {
	Email string `json:"email"`
	Pwd   string `json:"pwd"`
}

func (u *UserHandler) Login(ctx *gin.Context) {
	var req LoginRequest
	if err := ctx.ShouldBindJSON(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"msg": "参数不正确!"})
		return
	}

	// 校验邮箱和密码是否为空
	if req.Email == "" || req.Pwd == "" {
		ctx.JSON(http.StatusBadRequest, gin.H{"msg": "邮箱或密码不能为空"})
		return
	}

	// 正则校验邮箱
	ok, err := u.emailExp.MatchString(req.Email)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "系统错误!"})
		return
	}
	if !ok {
		ctx.JSON(http.StatusBadRequest, gin.H{"msg": "邮箱格式不正确"})
		return
	}

	// 校验密码格式
	ok, err = u.passwordExp.MatchString(req.Pwd)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"msg": "系统错误!"})
		return
	}
	if !ok {
		ctx.JSON(http.StatusBadRequest, gin.H{"msg": "密码必须大于8位,包含数字、特殊字符"})
		return
	}

	// 校验邮箱和密码是否匹配特定的值来确定登录成功与否
	if req.Email != "123@qq.com" || req.Pwd != "hello#world123" {
		ctx.JSON(http.StatusBadRequest, gin.H{"msg": "邮箱或密码不匹配!"})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"msg": "登录成功!"})
}

func InitWebServer(userHandler *UserHandler) *gin.Engine {
	server := gin.Default()
	userHandler.RegisterRoutes(server)
	return server
}

func main() {
	uh := &UserHandler{}
	server := InitWebServer(uh)
	server.Run(":8080") // 在8080端口启动服务器
}

1.2 介绍

在 Web 开发场景下,单元测试经常需要模拟 HTTP 请求和响应。使用 httptest 可以让我们在测试代码中创建一个 HTTP 服务器实例,并定义特定的请求和响应行为,从而模拟真实世界的网络交互,在Go语言中,一般都推荐使用Go标准库 net/http/httptest 进行测试。

1.3 基本用法

使用 httptest 的基本步骤如下:

  1. 导入 net/http/httptest 包。
  2. 创建一个 httptest.Server 实例,并指定你想要的服务器行为。
  3. 在测试代码中使用 httptest.NewRequest 创建一个模拟的 HTTP 请求,并将其发送到 httptest.Server
  4. 检查响应内容或状态码是否符合预期。

以下是一个简单的 httptest 用法示例

package main

import (
	"bytes"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestUserHandler_Login(t *testing.T) {
	// 定义测试用例
	testCases := []struct {
		name     string
		reqBody  string
		wantCode int
		wantBody string
	}{
		{
			name:     "登录成功",
			reqBody:  `{"email": "123@qq.com", "pwd": "hello#world123"}`,
			wantCode: http.StatusOK,
			wantBody: `{"msg": "登录成功!"}`,
		},
		{
			name:     "参数不正确",
			reqBody:  `{"email": "123@qq.com", "pwd": "hello#world123",}`,
			wantCode: http.StatusBadRequest,
			wantBody: `{"msg": "参数不正确!"}`,
		},
		{
			name:     "邮箱或密码为空",
			reqBody:  `{"email": "", "pwd": ""}`,
			wantCode: http.StatusBadRequest,
			wantBody: `{"msg": "邮箱或密码不能为空"}`,
		},
		{
			name:     "邮箱格式不正确",
			reqBody:  `{"email": "invalidemail", "pwd": "hello#world123"}`,
			wantCode: http.StatusBadRequest,
			wantBody: `{"msg": "邮箱格式不正确"}`,
		},
		{
			name:     "密码格式不正确",
			reqBody:  `{"email": "123@qq.com", "pwd": "invalidpassword"}`,
			wantCode: http.StatusBadRequest,
			wantBody: `{"msg": "密码必须大于8位,包含数字、特殊字符"}`,
		},
		{
			name:     "邮箱或密码不匹配",
			reqBody:  `{"email": "123123@qq.com", "pwd": "hello#world123"}`,
			wantCode: http.StatusBadRequest,
			wantBody: `{"msg": "邮箱或密码不匹配!"}`,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// 创建一个 gin 的上下文
			server := gin.Default()
			h := NewUserHandler()
			h.RegisterRoutes(server)
			// mock 创建一个 http 请求
			req, err := http.NewRequest(
				http.MethodPost,                     // 请求方法
				"/user/login",                       // 请求路径
				bytes.NewBuffer([]byte(tc.reqBody)), // 请求体
			)
			// 断言没有错误
			assert.NoError(t, err)
			// 设置请求头
			req.Header.Set("Content-Type", "application/json")
			// 创建一个响应
			resp := httptest.NewRecorder()
			// 服务端处理请求
			server.ServeHTTP(resp, req)
			// 断言响应码和响应体
			assert.Equal(t, tc.wantCode, resp.Code)
			// 断言 JSON 字符串是否相等
			assert.JSONEq(t, tc.wantBody, resp.Body.String())
		})
	}
}

在这个例子中,我们创建了一个简单的 HTTP 请求,TestUserHandler_Login 函数定义了一个测试函数,用于测试用户登录功能的不同情况。

  1. testCases 列表定义了多个测试用例,每个测试用例包含了测试名称、请求体、期望的 HTTP 状态码和期望的响应体内容。
  2. 使用 for 循环遍历测试用例列表,每次循环创建一个新的测试子函数,并在其中模拟 HTTP 请求发送给登录接口。
  3. 在每个测试子函数中,先创建一个 Gin 的默认上下文和用户处理器 UserHandler,然后注册路由并创建一个模拟的 HTTP 请求。
  4. 通过 httptest.NewRecorder() 创建一个响应记录器,使用 server.ServeHTTP(resp, req) 处理模拟请求,得到响应结果。
  5. 最后使用断言来验证实际响应的 HTTP 状态码和响应体是否与测试用例中的期望一致。

最后,使用Goland 运行测试,结果如下:

二、gock

2.1介绍

gock 可以帮助你在测试过程中模拟 HTTP 请求和响应,这对于测试涉及外部 API 调用的应用程序非常有用。它可以让你轻松地定义模拟请求,并验证你的应用程序是否正确处理了这些请求。

GitHub 地址:github.com/h2non/gock

2.2 安装

你可以通过以下方式安装 gock:

go get -u github.com/h2non/gock

导入 gock 包:

import "github.com/h2non/gock"

2.3 基本使用

gock 的基本用法如下:

  1. 启动拦截器:在测试开始前,使用 gock.New 函数启动拦截器,并指定你想要拦截的域名和端口。
  2. 定义拦截规则:你可以使用 gock.Intercept 方法来定义拦截规则,比如拦截特定的 URL、方法、头部信息等。
  3. 设置响应:你可以使用 gock.NewJsongock.NewText 等方法来设置拦截后的响应内容。
  4. 运行测试:在定义了拦截规则和响应后,你可以运行测试,gock 会拦截你的 HTTP 请求,并返回你设置的响应。

2.4 举个例子

2.4.1 前置代码

如果我们是在代码中请求外部API的场景(比如通过API调用其他服务获取返回值)又该怎么编写单元测试呢?

例如,我们有以下业务逻辑代码,依赖外部API:http://your-api.com/post提供的数据。

// ReqParam API请求参数
type ReqParam struct {
	X int `json:"x"`
}

// Result API返回结果
type Result struct {
	Value int `json:"value"`
}

func GetResultByAPI(x, y int) int {
	p := &ReqParam{X: x}
	b, _ := json.Marshal(p)

	// 调用其他服务的API
	resp, err := http.Post(
		"http://your-api.com/post",
		"application/json",
		bytes.NewBuffer(b),
	)
	if err != nil {
		return -1
	}
	body, _ := ioutil.ReadAll(resp.Body)
	var ret Result
	if err := json.Unmarshal(body, &ret); err != nil {
		return -1
	}
	// 这里是对API返回的数据做一些逻辑处理
	return ret.Value + y
}

在对类似上述这类业务代码编写单元测试的时候,如果不想在测试过程中真正去发送请求或者依赖的外部接口还没有开发完成时,我们可以在单元测试中对依赖的API进行mock。

2.4.2 测试用例

使用gock对外部API进行mock,即mock指定参数返回约定好的响应内容。 下面的代码中mock了两组数据,组成了两个测试用例。

package gock_demo

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"gopkg.in/h2non/gock.v1"
)

func TestGetResultByAPI(t *testing.T) {
	defer gock.Off() // 测试执行后刷新挂起的mock

	// mock 请求外部api时传参x=1返回100
	gock.New("http://your-api.com").
		Post("/post").
		MatchType("json").
		JSON(map[string]int{"x": 1}).
		Reply(200).
		JSON(map[string]int{"value": 100})

	// 调用我们的业务函数
	res := GetResultByAPI(1, 1)
	// 校验返回结果是否符合预期
	assert.Equal(t, res, 101)

	// mock 请求外部api时传参x=2返回200
	gock.New("http://your-api.com").
		Post("/post").
		MatchType("json").
		JSON(map[string]int{"x": 2}).
		Reply(200).
		JSON(map[string]int{"value": 200})

	// 调用我们的业务函数
	res = GetResultByAPI(2, 2)
	// 校验返回结果是否符合预期
	assert.Equal(t, res, 202)

	assert.True(t, gock.IsDone()) // 断言mock被触发
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/551440.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

腾讯实验室推出类似 Sora 的长视频生成Mira;阿里巴巴推出强大的代码生成模型CodeQwen1.5

✨ 1: Mira 腾讯PCG ARC实验室推出Mira:类似 Sora 的长视频生成 Mira(Mini-Sora),是一个尝试生成高质量、长时视频的初步探索项目,以Sora风格进行长视频生成。与现有的文本到视频(Text-to-Video, T2V&a…

2024年【高处安装、维护、拆除】试题及解析及高处安装、维护、拆除模拟试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年【高处安装、维护、拆除】试题及解析及高处安装、维护、拆除模拟试题,包含高处安装、维护、拆除试题及解析答案和解析及高处安装、维护、拆除模拟试题练习。安全生产模拟考试一点通结合国家高处安装…

基于SSM+Jsp+Mysql的贝儿米幼儿教育管理系统

开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包…

如何简单下载指定版本的jdk

Oracle 官方提供的 Java Development Kit (JDK) 的归档站点。它主要用于存放历史版本的 JDK,供开发者下载和使用。 附上站点地址:Archived OpenJDK GA Releases 在这个站点可以找到各版本的jdk,简单实用~ 找到版本,点击tar.gz进…

研究表明,全球互联网流量竟有一半来自机器人

据Cyber News消息,Thales Imperva Bad Bot近期做了一份报告,显示在2023年有49.6%的互联网流量竟来自机器人,比上一年增长 2%,达到自2013年以来观察到的最高水平。 报告称,这一趋势正对企业组织产生负面影响&#xff0c…

Qwen1.5大语言模型微调实践

在人工智能领域,大语言模型(Large Language Model,LLM)的兴起和广泛应用,为自然语言处理(NLP)带来了前所未有的变革。Qwen1.5大语言模型作为其中的佼佼者,不仅拥有强大的语言生成和理…

Seata

Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。 1.1 Seata的三大角色 在 Seata 的架构中,一共有三个角色&#xff1…

解决 MSYS2 Qt 6.7 默认 stylesheet 在 windows 11 下的显示故障

项目场景: MSYS2 升级到 Qt6.7.0,发现显示故障,所有Qt6程序以及 QtCreator的SpinBox都显示不全,Combox的底色不对。 问题描述 2024年4月1日,pacman升级MSYS2后,Qt6遇到风格错误。如果使用官方的 Qt onlin…

C++ ─── 类和对象(拷贝构造函数)

目录 拷贝构造函数 特征 结论: 拷贝构造函数 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。 特征 拷贝构造函数也是特殊的成员函数&…

【Redis 神秘大陆】004 高可用集群

四、Redis 高可用和集群 当你发现这些内容对你有帮助时,为了支持我的工作,不妨给一个免费的⭐Star,这将是对我最大的鼓励!感谢你的陪伴与支持!一起在技术的路上共同成长吧!点击链接:GitHub | G…

野生动物保护视频AI智能监管方案,撑起智能保护伞,守护野生动物

一、背景 在当今世界,野生动物保护已经成为全球性的重要议题。然而,由于野生动物生存环境的不断恶化以及非法狩猎等活动的盛行,保护野生动物变得尤为迫切。为了更有效地保护野生动物,利用视频智能监管技术成为一种可行的方案。 …

python读取DBF数据

DBF文件通常是由数据库软件(如FoxPro或dBASE)创建的数据库文件。Python中并没有直接读取DBF文件的内置库,但你可以使用第三方库如dbfread来读取DBF文件。 首先,你需要安装dbfread库。你可以使用pip来安装: pip insta…

数据结构——队列(C++实现)

数据结构——队列 什么是队列顺序队列链式队列实现 今天我们接着来看队列: 什么是队列 队列是一种基础且广泛应用的数据结构,它具有以下核心特征: 定义: 线性表:队列是一种特殊的线性表,其中的元素呈线…

宝宝洗衣机买几公斤?四款精心挑选实用婴儿洗衣机推荐

家里有孩子的,条件允许的话,婴儿洗衣机还是非常有必要买的。由于宝宝的年纪还小,使得宝宝的皮肤比较娇嫩,与成人衣物分开洗护,可以为宝宝带来更加健康的生长环境,并且可以避免与大人衣物混洗所带来的细菌的…

【训练营】DateWhale——动手学大模型应用开发(更新中)

文章目录 写在前面大模型简介LLM简介RAG简介LangChain开发框架开发LLM应用的整体流程 写在前面 大模型时代从GPT爆发开始到现在已有一年多了,深度学习发展之快无法想象,一味感叹技术发展速度超越个人学习速度是没用的,倒不如花点时间参加一些…

其它IO合集

其它IO合集 1. 缓冲流1.1 概述1.2 字节缓冲流构造方法效率测试 1.3 字符缓冲流构造方法特有方法 2. 转换流2.1 字符编码和字符集字符编码字符集 2.2 编码引出的问题2.3 InputStreamReader类构造方法指定编码读取 2.4 OutputStreamWriter类构造方法指定编码写出转换流理解图解 3…

网络协议——IS-IS协议详解

1. IS-IS是什么 IS-IS是一种基于链路状态并使用最短路径优先算法进行路由计算的一种IGP协议。IS-IS属于内部网关协议,用于自治系统内部。IS-IS是一种链路状态协议,使用最短路径优先算法进行路由计算。 2. 应用场景(园区网和骨干网&#xff0…

冯诺依曼与进程【Linux】

文章目录 冯诺依曼体系结构(从硬件的角度描述)冯诺依曼体系结构(从软件的角度描述)操作系统(软件)理解管理系统调用和库函数进程查看进程的两种方式 通过系统调用获取进程的PID和PPID通过系统调用创建进程-…

RAG学习笔记系列(一)

RAG 介绍 RAG 全称为 Retrieval Augmented Generation(检索增强生成)。是基于LLM构建系统的一种架构。 RAG 基本上可以理解为:搜索 LLM prompting。根据用户的查询语句,系统会先使用搜索算法获取到相关内容作为上下文&#xff0…

IMU应用于膝关节功能评估

近日,来自中国的研究团队开发了一款基于IMU的可穿戴系统,用于评估膝关节骨关节炎引发的功能障碍。研究着重重验证该系统在测量步态及下肢功能方面的准确性,通过对比业界公认的运动捕捉和步态分析系统,评估IMU传感器在这一领域的性…