Go 單元測試之HTTP請求與API測試

来源:https://www.cnblogs.com/taoxiaoxin/p/18141253
-Advertisement-
Play Games

目錄一、httptest1.1 前置代碼準備1.2 介紹1.3 基本用法二、gock2.1介紹2.2 安裝2.3 基本使用2.4 舉個例子2.4.1 前置代碼2.4.2 測試用例 一、httptest 1.1 前置代碼準備 假設我們的業務邏輯是搭建一個http server端,對外提供HTTP服務。 ...


目錄

一、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 != "[email protected]" || 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": "[email protected]", "pwd": "hello#world123"}`,
			wantCode: http.StatusOK,
			wantBody: `{"msg": "登錄成功!"}`,
		},
		{
			name:     "參數不正確",
			reqBody:  `{"email": "[email protected]", "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": "[email protected]", "pwd": "invalidpassword"}`,
			wantCode: http.StatusBadRequest,
			wantBody: `{"msg": "密碼必須大於8位,包含數字、特殊字元"}`,
		},
		{
			name:     "郵箱或密碼不匹配",
			reqBody:  `{"email": "[email protected]", "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被觸發
}
分享是一種快樂,開心是一種態度!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • splice在英語中的意思是拼接,在實際的代碼使用中,splice就在數組中起到了一個拼接的作用 使用方法 splice(x,y,a,b,c,...) 其中x、y為數字,a、b、c為新添加的項,意思是從數組的第x項開始刪除y項,併在其中添加a、b、c...,其中x、y必填,abc可不填 圖像理解 現 ...
  • 前言 2018年剛入行前端時,公司使用的還是Angular。Angular什麼都好,就是寫代碼時的體驗老糟心了,改一個地方,按下保存之後,要等好幾秒刷新後才能看到效果,Webstorm無比好用的自動保存,對我來說反而像是一個負擔。然而2024年了,Angular已經更新了17版本,還是沒有解決這個問 ...
  • 這兩天為了重啟五年前基於 React Native(版本 0.59.9)開發的老項目,經過各種填坑查詢等操作,最終把它成功地運行起來了。 在這篇文章中,我將詳述那些遭遇的挑戰以及對應的解決方案,以期為同樣面臨此類困境的開發者提供寶貴的經驗參考。 這個項目涉及到的環境基本版本信息如下: react: ...
  • 引言: 近年來,隨著教育理念的提升,對學生綜合素質的教育越發重視,特別是越發重視學生的身體素質提升,各階段的升學考試也將體測納入考核範圍。學校也推出了各種體測鍛煉促進手段,今天為您介紹一個基於小程式的,線上AI體測訓練打卡、評測方案。 一、體測功能需求 根據相關學生體測標準,體測小程式需要具備以下功 ...
  • 在網上一直流傳著一個爭論不休的話題:金額到底是用Long還是用BigDecimal?這個話題一齣在哪都會引起異常無比激烈的討論。。。。 比如說這個觀點:算錢用BigDecimal是常識 有支持用Long的,將金額的單位設計為分,然後乘以100,使用Long進行存儲以及計算,這樣不用擔心小數點問題。 ...
  • 用python開發的小紅書關鍵詞搜索軟體,採集欄位包含:關鍵詞, 頁碼, 筆記id, 筆記鏈接, 筆記標題, 筆記類型, 點贊數, 用戶id, 用戶主頁鏈接, 用戶昵稱。 ...
  • 目錄一、 sqlmock介紹二、安裝三、基本用法四、一個小案例五、Gorm 初始化註意點 一、 sqlmock介紹 sqlmock 是一個用於測試資料庫交互的 Go 模擬庫。它可以模擬 SQL 查詢、插入、更新等操作,並且可以驗證 SQL 語句的執行情況,非常適合用於單元測試中。 二、安裝 go g ...
  • shell腳本中的運算符和條件判斷: 一、算術運算符 在Shell腳本中,你可以使用各種運算符來執行數學運算、比較和邏輯操作。 計算方式: $[ ] $(( )) 例: a=$[(9+5)90] 列印輸出結果 ==> echo $a 二、條件判斷 判斷方式: test $a = 90 [ $a = ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...