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
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...