最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在Go語言單元測試中解決HTTP網(wǎng)絡(luò)依賴問題

 更新時間:2023年07月24日 11:21:42   作者:江湖十年  
在 Go 語言中,我們需要找到一種可靠的方法來測試 HTTP 請求和響應(yīng),本文將探討在 Go 中進行 HTTP 應(yīng)用測試時,如何解決應(yīng)用程序的依賴問題,以確保我們能夠編寫出可靠的測試用例,需要的朋友可以參考下

在開發(fā) Web 應(yīng)用程序時,確保 HTTP 功能的正確性是至關(guān)重要的。然而,由于 Web 應(yīng)用程序通常涉及到與外部依賴的交互,編寫 HTTP 請求和響應(yīng)的有效測試變得具有挑戰(zhàn)性。在進行單元測試時,我們必須思考如何解決被測程序的外部依賴問題。

HTTP Server 測試

首先,我們來看下,站在 HTTP Server 端的角度,如何編寫應(yīng)用程序的測試代碼。

假設(shè)我們有一個 HTTP Server 對外提供服務(wù),代碼如下:

package main
import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"github.com/julienschmidt/httprouter"
)
type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
var users = []User{
	{ID: 1, Name: "user1"},
}
func CreateUserHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	...
}
func GetUserHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	...
}
func setupRouter() *httprouter.Router {
	router := httprouter.New()
	router.POST("/users", CreateUserHandler)
	router.GET("/users/:id", GetUserHandler)
	return router
}
func main() {
	router := setupRouter()
	_ = http.ListenAndServe(":8000", router)
}

這個服務(wù)監(jiān)聽 8000 端口,分別提供了兩個 HTTP 接口:

POST /users 用來創(chuàng)建用戶。

GET /users/:id 用來獲取指定 ID 對應(yīng)的用戶信息。

為了保證業(yè)務(wù)的正確性,我們需要對 CreateUserHandlerGetUserHandler 這兩個 Handler 進行單元測試。

我們先來看下用于創(chuàng)建用戶的 CreateUserHandler 函數(shù)是如何定義的:

func CreateUserHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	w.Header().Set("Content-Type", "application/json")
	body, err := io.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		_, _ = fmt.Fprintf(w, `{"msg":"%s"}`, err.Error())
		return
	}
	defer func() { _ = r.Body.Close() }()
	u := User{}
	if err := json.Unmarshal(body, &u); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = fmt.Fprintf(w, `{"msg":"%s"}`, err.Error())
		return
	}
	u.ID = users[len(users)-1].ID + 1
	users = append(users, u)
	w.WriteHeader(http.StatusCreated)
}

在這個 Handler 中,首先寫入響應(yīng)頭 Content-Type: application/json,表示創(chuàng)建用戶的響應(yīng)內(nèi)容為 JSON 格式。

接著從請求體 r.Body 中讀取客戶端提交的用戶信息。

如果讀取請求體失敗,則寫入響應(yīng)狀態(tài)碼 400,表示客戶端提交的用戶信息有誤,并返回 JSON 錯誤響應(yīng)。

接著,使用 json.Unmarshal 對請求體進行 JSON 解碼,將數(shù)據(jù)填入 User 結(jié)構(gòu)體中。

如果 JSON 解碼失敗,則寫入響應(yīng)狀態(tài)碼 500,表示服務(wù)端出現(xiàn)了錯誤,并返回 JSON 錯誤響應(yīng)。

最終,將新創(chuàng)建的用戶信息保存到 users 切片中,并寫入響應(yīng)狀態(tài)碼 201,表示用戶創(chuàng)建成功。注意,根據(jù) RESTful 規(guī)范,這里并不需要返回響應(yīng)體。

下面,我們來分析下如何對這個 Handler 函數(shù)編寫單元測試代碼。

首先,我們思考下 CreateUserHandler 這個函數(shù)都有哪些外部依賴?

從函數(shù)參數(shù)來看,我們需要一個用來表示 HTTP 響應(yīng)的 http.ResponseWriter,一個用來表示 HTTP 請求的 *http.Request,以及一個用來記錄 HTTP 請求路由參數(shù)的 httprouter.Params。

在函數(shù)內(nèi)部,則依賴了全局變量 users。

知道了這些外部依賴,那么,我們?nèi)绾尉帉憜卧獪y試才能解決這些外部依賴呢?

最直接的辦法,就是啟動這個 Web Server,然后在單元測試代碼中對 POST /users 接口發(fā)送一個 HTTP 請求,之后判斷程序的 HTTP 響應(yīng)結(jié)果以及 users 變量中的數(shù)據(jù),來驗證 CreateUserHandler 函數(shù)的正確性。

但這種做法顯然超出了單元測試的范疇,更像是在做集成測試。單元測試的一個主要特征就是要隔離外部依賴,使用測試替身來替換依賴。

所以,我們應(yīng)該想辦法來制作測試替身

我們先從最簡單的 users 變量開始,想辦法在測試過程中替換掉 users。

users 僅是一個切片變量,用來保存用戶數(shù)據(jù),我們可以編寫一個函數(shù),將其內(nèi)容替換成測試數(shù)據(jù),代碼如下:

func setupTestUser() func() {
	defaultUsers := users
	users = []User{
		{ID: 1, Name: "test-user1"},
	}
	return func() {
		users = defaultUsers
	}
}

setupTestUser 函數(shù)內(nèi)部為全局變量 users 進行了重新賦值,并返回一個匿名函數(shù),這個匿名函數(shù)可以將 users 變量值恢復(fù)。

在測試期間可以這樣使用:

func TestCreateUserHandler(t *testing.T) {
	cleanup := setupTestUser()
	defer cleanup()
	...
}

在測試最開始時調(diào)用 setupTestUser 來初始化測試數(shù)據(jù),使用 defer 語句實現(xiàn)測試函數(shù)退出時恢復(fù) users 數(shù)據(jù)。

接下來,我們需要構(gòu)造一個表示 HTTP 響應(yīng)的 http.ResponseWriter。

幸運的是,這并不需要費多少力氣,Go 語言官方早就想到了這個訴求,為我們提供了 net/http/httptest 標準庫,這個庫實現(xiàn)了一些專門用來進行網(wǎng)絡(luò)測試的實用工具。

構(gòu)造一個測試用的 HTTP 響應(yīng)對象僅需一行代碼就能完成:

w := httptest.NewRecorder()

得到的 w 變量實現(xiàn)了 http.ResponseWriter 接口,可以直接傳遞給 Handler 函數(shù)。

要想構(gòu)造一個表示 HTTP 請求的 *http.Request 對象,同樣非常簡單:

body := strings.NewReader(`{"name": "user2"}`)
req := httptest.NewRequest("POST", "/users", body)

使用 httptest.NewRequest 創(chuàng)建的 req 變量正是 *http.Request 類型,它包含了請求方法、路徑、請求體。

現(xiàn)在,我們只差一個用來記錄 HTTP 請求路由參數(shù)的 httprouter.Params 類型對象沒有構(gòu)造了。

httprouter.Params 是由 httprouter 這個第三方包提供的,httprouter 是一個高性能的 HTTP 路由,兼容 net/http 標準庫。

它提供了 (*httprouter.Router).ServeHTTP 方法,可以調(diào)用請求對應(yīng)的 Handler 函數(shù)。即可以根據(jù)請求對象 *http.Request,自動調(diào)用 CreateUserHandler 函數(shù)。

在調(diào)用 Handler 函數(shù)時,httprouter 會解析請求中的路由參數(shù)保存在 httprouter.Params 對象中并傳給 Handler,所以這個對象無需我們手動構(gòu)造。

現(xiàn)在,單元測試函數(shù)的邏輯就清晰了:

func TestCreateUserHandler(t *testing.T) {
	cleanup := setupTestUser()
	defer cleanup()
	w := httptest.NewRecorder()
	body := strings.NewReader(`{"name": "user2"}`)
	req := httptest.NewRequest("POST", "/users", body)
	router := setupRouter()
	router.ServeHTTP(w, req)
}

根據(jù)前文的講解,我們構(gòu)造了單元測試所需的依賴項。

setupRouter() 返回 *httprouter.Router 對象,當代碼執(zhí)行到 router.ServeHTTP(w, req) 時,就會根據(jù)傳遞的 req 參數(shù),自動調(diào)用與之匹配的 Handler,即被測試函數(shù) CreateUserHandler。

接下來,我們要做的就是判斷 CreateUserHandler 函數(shù)執(zhí)行后的結(jié)果是否正確。

完整單元測試代碼如下:

package main
import (
	"encoding/json"
	"net/http/httptest"
	"strings"
	"testing"
	"github.com/stretchr/testify/assert"
)
func TestCreateUserHandler(t *testing.T) {
	cleanup := setupTestUser()
	defer cleanup()
	w := httptest.NewRecorder()
	body := strings.NewReader(`{"name": "user2"}`)
	req := httptest.NewRequest("POST", "/users", body)
	router := setupRouter()
	router.ServeHTTP(w, req)
	assert.Equal(t, 201, w.Code)
	assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
	assert.Equal(t, "", w.Body.String())
	assert.Equal(t, 2, len(users))
	u2, _ := json.Marshal(users[1])
	assert.Equal(t, `{"id":2,"name":"user2"}`, string(u2))
}

這里引入了第三方包 testify 用來進行斷言操作,assert.Equal 能夠判斷兩個對象是否相等,這可以簡化代碼,不再需要使用 if 來判斷了。更多關(guān)于 testify 包的使用,可以查看官方文檔

我們首先斷言了響應(yīng)狀態(tài)碼是否為 201。

接著又斷言了響應(yīng)頭的 Content-Type 字段是否為 application/json。

然后判斷了響應(yīng)內(nèi)容是否為空。

最后,通過 users 中的值來判斷用戶信息是否保存正確。

使用 go test 來執(zhí)行測試函數(shù):

$ go test -v -run="TestCreateUserHandler" . 
=== RUN   TestCreateUserHandler
--- PASS: TestCreateUserHandler (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/http/server      0.544s

測試通過。

至此,我們成功為 CreateUserHandler 函數(shù)編寫了一個單元測試。

不過,這個單元測試僅覆蓋了正常邏輯,CreateUserHandler 方法返回 400500 兩種狀態(tài)碼的邏輯沒有被測試覆蓋,這兩種場景就留做作業(yè)你自己來完成吧。

接下來,我們再為獲取用戶信息的函數(shù) GetUserHandler 編寫一個單元測試。

先來看下 GetUserHandler 函數(shù)的定義:

func GetUserHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	userID, _ := strconv.Atoi(ps[0].Value)
	w.Header().Set("Content-Type", "application/json")
	for _, u := range users {
		if u.ID == userID {
			user, _ := json.Marshal(u)
			_, _ = w.Write(user)
			return
		}
	}
	w.WriteHeader(http.StatusNotFound)
	_, _ = w.Write([]byte(`{"msg":"notfound"}`))
}

獲取用戶信息的邏輯,相對簡單一點。

首先從 HTTP 請求的路徑參數(shù)中獲取用戶 ID。

然后判斷這個 ID 對應(yīng)的用戶信息是否存在,如果存在就返回用戶信息。

不存在,則寫入 404 狀態(tài)碼,并返回 notfound 信息。

有了前文對 CreateUserHandler 函數(shù)編寫測試的經(jīng)驗,想必如何對 GetUserHandler 函數(shù)進行測試你已經(jīng)輕車熟路了。

以下是我為其編寫的測試代碼:

func TestGetUserHandler(t *testing.T) {
	cleanup := setupTestUser()
	defer cleanup()
	type want struct {
		code int
		body string
	}
	tests := []struct {
		name string
		args int
		want want
	}{
		{
			name: "get test-user1",
			args: 1,
			want: want{
				code: 200,
				body: `{"id":1,"name":"test-user1"}`,
			},
		},
		{
			name: "get user not found",
			args: 2,
			want: want{
				code: 404,
				body: `{"msg":"notfound"}`,
			},
		},
	}
	router := setupRouter()
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest("GET", fmt.Sprintf("/users/%d", tt.args), nil)
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)
			assert.Equal(t, tt.want.code, w.Code)
			assert.Equal(t, tt.want.body, w.Body.String())
		})
	}
}

獲取用戶信息的單元測試代碼,在測試執(zhí)行開始,同樣使用 setupTestUser 函數(shù)來初始化測試數(shù)據(jù),并使用 defer 來完成數(shù)據(jù)恢復(fù)。

這次為了提高測試覆蓋率,我對 GetUserHandler 函數(shù)的正常響應(yīng)以及返回 404 狀態(tài)碼的異常響應(yīng)場景都進行了測試。

這里使用了表格測試,

除了使用表格測試的形式,其他測試邏輯與 CreateUserHandler 的單元測試邏輯基本相同,我就不過多介紹了。

使用 go test 來執(zhí)行測試函數(shù):

$ go test -v -run="TestGetUserHandler" .
=== RUN   TestGetUserHandler
=== RUN   TestGetUserHandler/get_test-user1
=== RUN   TestGetUserHandler/get_user_not_found
--- PASS: TestGetUserHandler (0.00s)
    --- PASS: TestGetUserHandler/get_test-user1 (0.00s)
    --- PASS: TestGetUserHandler/get_user_not_found (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/http/server      0.516s

表格測試的兩個用例都通過了測試。

HTTP Client 測試

接下來,我們來看下,站在 HTTP Client 端的角度,如何編寫應(yīng)用程序的測試代碼。

假設(shè)我們有一個進程監(jiān)控程序,能夠檢測某個進程是否正在執(zhí)行,如果進程退出,就發(fā)送一條消息通知到飛書群。

代碼如下:

package main
import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"syscall"
	"time"
)
func monitor(pid int) (*Result, error) {
	for {
		// 檢查進程是否存在
		err := syscall.Kill(pid, 0)
		if err != nil {
			log.Printf("Process %d exited\n", pid)
			webhook := os.Getenv("WEBHOOK")
			return sendFeishu(fmt.Sprintf("Process %d exited", pid), webhook)
		}
		log.Printf("Process %d is running\n", pid)
		time.Sleep(1 * time.Second)
	}
}
func main() {
	if len(os.Args) != 2 {
		log.Println("Usage: ./monitor <pid>")
		return
	}
	pid, err := strconv.Atoi(os.Args[1])
	if err != nil {
		log.Printf("Invalid pid: %s\n", os.Args[1])
		return
	}
	result, err := monitor(pid)
	if err != nil {
		log.Fatal(err)
	}
	log.Println(result)
}

這個程序可以通過 ./monitor <pid> 形式啟動。

monitor 函數(shù)內(nèi)部有一個循環(huán),會根據(jù)傳遞進來的進程 PID 不斷的來檢測對應(yīng)進程是否存在。

如果不存在,則說明進程已經(jīng)停止,然后調(diào)用 sendFeishu 函數(shù)發(fā)送消息通知到指定的飛書 webhook 地址。

monitor 函數(shù)會將 sendFeishu 函數(shù)的返回結(jié)果原樣返回。

sendFeishu 函數(shù)實現(xiàn)如下:

type Message struct {
	Content struct {
		Text string `json:"text"`
	} `json:"content"`
	MsgType string `json:"msg_type"`
}
type Result struct {
	StatusCode    int    `json:"StatusCode"`
	StatusMessage string `json:"StatusMessage"`
	Code          int    `json:"code"`
	Data          any    `json:"data"`
	Msg           string `json:"msg"`
}
func sendFeishu(content, webhook string) (*Result, error) {
	msg := Message{
		Content: struct {
			Text string `json:"text"`
		}{
			Text: content,
		},
		MsgType: "text",
	}
	body, _ := json.Marshal(msg)
	resp, err := http.Post(webhook, "application/json", bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	defer func() { _ = resp.Body.Close() }()
	result := new(Result)
	if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
		return nil, err
	}
	if result.Code != 0 {
		return nil, fmt.Errorf("code: %d, error: %s", result.Code, result.Msg)
	}
	return result, nil
}

sendFeishu 函數(shù)能夠?qū)鬟f進來的消息發(fā)送到指定的 webhook 地址。

至于內(nèi)部具體邏輯,我們并不需要關(guān)心,只當作第三方包來使用即可,僅需要知道它最終會返回 *Result 對象。

現(xiàn)在我們需要對 monitor 函數(shù)進行測試。

我們同樣需要先分析下 monitor 函數(shù)的外部依賴是什么。

首先 monitor 函數(shù)的參數(shù) pid 是一個 int 類型,不難構(gòu)造。

monitor 函數(shù)內(nèi)部調(diào)用了 sendFeishu 函數(shù),并且將 sendFeishu 的返回結(jié)果原樣返回,所以 sendFeishu 函數(shù)是一個外部依賴。

另外,傳遞個給 sendFeishu 函數(shù)的 webhook 地址是從環(huán)境變量中獲取的,這也算是一個外部依賴。

所以要測試 monitor 函數(shù),我們需要使用測試替身來解決這兩個外部依賴項。

對于環(huán)境變量的依賴很好解決,Go 提供了 os.Setenv 可以在程序中動態(tài)設(shè)置環(huán)境變量的值。

對于另一個依賴項 sendFeishu 函數(shù),它又依賴了 webhook 地址所對應(yīng)的 HTTP Server。

所以我們需要解決 HTTP Server 的依賴問題。

針對 HTTP Server,Go 標準庫 net/http/httptest 同樣提供了對應(yīng)工具。

我們可以使用 httptest.NewServer 創(chuàng)建一個測試用的 HTTP Server:

func newTestServer() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		switch r.RequestURI {
		case "/success":
			_, _ = fmt.Fprintf(w, `{"StatusCode":0,"StatusMessage":"success","code":0,"data":{},"msg":"success"}`)
		case "/error":
			_, _ = fmt.Fprintf(w, `{"code":19001,"data":{},"msg":"param invalid: incoming webhook access token invalid"}`)
		}
	}))
}

newTestServer 函數(shù)返回一個用于測試的 HTTP Server 對象。

newTestServer 函數(shù)內(nèi)部,定義了兩個路由 /success/error,分別來處理成功響應(yīng)和失敗響應(yīng)兩種情況。

與前文介紹的 setupTestUser 函數(shù)一樣,我們需要在測試程序開始執(zhí)行時準備測試數(shù)據(jù),即啟動這個測試用的 HTTP Server,在測試程序執(zhí)行完成后清理數(shù)據(jù),即關(guān)閉 HTTP Server。

不過,這次我們不再使用 setupTestUser 函數(shù)結(jié)合 defer cleanup() 的方式,而是換種方式來實現(xiàn):

var ts *httptest.Server
func TestMain(m *testing.M) {
	ts = newTestServer()
	m.Run()
	ts.Close()
}

首先我們定義了一個全局變量 ts,用來保存測試用的 HTTP Server 對象。

然后在 TestMain 函數(shù)中調(diào)用 newTestServer 函數(shù)為 ts 變量賦值。

接下來執(zhí)行 m.Run() 方法。

最終調(diào)用 ts.Close() 關(guān)閉 HTTP Server。

TestMain 函數(shù)名不是隨意取的,而是 Go 單元測試中的一個約定名稱,它相當于 main 函數(shù),在使用 go test 命令執(zhí)行所有測試用例前,會優(yōu)先執(zhí)行 TestMain 函數(shù)。

TestMain 函數(shù)中調(diào)用 m.Run()(*testing.M).Run() 方法會執(zhí)行全部的測試用例。

當所有測試用例執(zhí)行完成后,代碼才會執(zhí)行到 ts.Close()。

所以,相較于 setupTestUser 函數(shù)在每個測試函數(shù)內(nèi)部都要調(diào)用一次的用法,TestMain 函數(shù)更加省力。不過這也決定了二者適用場景不同。TestMain 函數(shù)粒度更大,作用于全部測試用例,setupTestUser 函數(shù)只作用于單個測試函數(shù)。

現(xiàn)在,我們已經(jīng)解決了 monitor 函數(shù)的依賴項問題。

為其編寫的單元測試如下:

func Test_monitor(t *testing.T) {
	type args struct {
		pid     int
		webhook string
	}
	tests := []struct {
		name    string
		args    args
		want    *Result
		wantErr error
	}{
		{
			name: "process exited and send feishu success",
			args: args{
				pid:     10000000,
				webhook: ts.URL + "/success",
			},
			want: &Result{
				StatusCode:    0,
				StatusMessage: "success",
				Code:          0,
				Data:          make(map[string]interface{}),
				Msg:           "success",
			},
		},
		{
			name: "process exited and send feishu error",
			args: args{
				pid:     20000000,
				webhook: ts.URL + "/error",
			},
			wantErr: errors.New("code: 19001, error: param invalid: incoming webhook access token invalid"),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_ = os.Setenv("WEBHOOK", tt.args.webhook)
			got, err := monitor(tt.args.pid)
			if err != nil {
				if tt.wantErr == nil || err.Error() != tt.wantErr.Error() {
					t.Errorf("monitor() error = %v, wantErr %v", err, tt.wantErr)
					return
				}
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("monitor() got = %v, want %v", got, tt.want)
			}
		})
	}
}

這里同樣采用表格測試的方式,有兩個測試用例,一個用于測試被檢測程序退出后發(fā)送飛書消息成功的情況,一個用于測試被檢測程序退出后發(fā)送飛書消息失敗的情況。

測試用例中 pid 被設(shè)置為很大的值,已經(jīng)超過了 Linux 系統(tǒng)允許的最大 pid 值,所以檢測結(jié)果一定是程序已經(jīng)退出。

由于被檢測程序不退出的情況,monitor 函數(shù)會一直循環(huán)檢測,邏輯比較簡單,就沒有對這個邏輯編寫測試用例。

使用 go test 來執(zhí)行測試函數(shù):

$ go test -v -run="^Test_monitor$" .
=== RUN   Test_monitor
=== RUN   Test_monitor/process_exited_and_send_feishu_success
2023/07/15 13:27:46 Process 10000000 exited
=== RUN   Test_monitor/process_exited_and_send_feishu_error
2023/07/15 13:27:46 Process 20000000 exited
--- PASS: Test_monitor (0.00s)
    --- PASS: Test_monitor/process_exited_and_send_feishu_success (0.00s)
    --- PASS: Test_monitor/process_exited_and_send_feishu_error (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/http/client      0.166s

測試通過。

以上,我們通過 net/http/httptest 提供的測試工具,在本地啟動了一個測試 HTTP Server,來解決被測試代碼依賴外部 HTTP 服務(wù)的問題。

有時候,我們不想真正的在本地啟動一個 HTTP Server,或者無法做到這一點。

那么,我們還有另一種方案來解決這個問題,可以使用 gock 來模擬 HTTP 服務(wù)。

gock 是 Go 社區(qū)中的一個第三方包,雖然不在本地啟動一個 HTTP Server,但是它能夠攔截所有被 mock 的 HTTP 請求。所以,我們能夠利用 gock 攔截 sendFeishu 函數(shù)發(fā)送給 webhook 地址的請求,然后返回 mock 數(shù)據(jù)。這樣,就可以使用 mock 的方式來解決依賴外部 HTTP 服務(wù)的問題。

使用 gock 編寫的單元測試代碼如下:

package main
import (
	"os"
	"testing"
	"github.com/h2non/gock"
	"github.com/stretchr/testify/assert"
)
func Test_monitor_by_gock(t *testing.T) {
	defer gock.Off() // Flush pending mocks after test execution
	gock.New("http://localhost:8080").
		Post("/webhook").
		Reply(200).
		JSON(map[string]interface{}{
			"StatusCode":    0,
			"StatusMessage": "success",
			"Code":          0,
			"Data":          make(map[string]interface{}),
			"Msg":           "success",
		})
	_ = os.Setenv("WEBHOOK", "http://localhost:8080/webhook")
	got, err := monitor(30000000)
	assert.NoError(t, err)
	assert.Equal(t, &Result{
		StatusCode:    0,
		StatusMessage: "success",
		Code:          0,
		Data:          make(map[string]interface{}),
		Msg:           "success",
	}, got)
	assert.True(t, gock.IsDone())
}

首先,在測試函數(shù)的開始,使用 defer 延遲調(diào)用 gock.Off(),可以保證在測試完成后刷新掛起的 mock,即還原被 mock 對象的初始狀態(tài)。

然后,我們使用 gock.New()http://localhost:8080 這個 URL 進行 mock,這樣 gock 會攔截測試過程中所有發(fā)送到這個地址的 HTTP 請求。

gock.New() 支持鏈式調(diào)用,.Post("/webhook") 表示攔截對 /webhook 這個 URL 的 POST 請求。

.Reply(200) 表示針對這個請求,返回 200 狀態(tài)碼。

.JSON(...) 即為返回的 JSON 格式響應(yīng)內(nèi)容。

接著,我們將 webhook 地址設(shè)置為 http://localhost:8080/webhook,這樣,在調(diào)用 sendFeishu 函數(shù)時發(fā)送的請求就會被攔截,并返回上一步中的 .JSON(...) 內(nèi)容。

之后就是調(diào)用 monitor 函數(shù),并斷言測試結(jié)果是否正確。

最后,調(diào)用 assert.True(t, gock.IsDone()) 來驗證已經(jīng)沒有掛起的 mock 了。

使用 go test 來執(zhí)行測試函數(shù):

$ go test -v -run="^Test_monitor_by_gock$" .
=== RUN   Test_monitor_by_gock
2023/07/15 13:28:22 Process 30000000 exited
--- PASS: Test_monitor_by_gock (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/http/client      0.574s

單元測試執(zhí)行通過。

總結(jié)

本文向大家介紹了在 Go 中編寫單元測試時,如何解決 HTTP 外部依賴的問題。

我們分別站在 HTTP 服務(wù)端和 HTTP 客戶端兩個角度,使用 net/http/httptest 標準庫和 gock 第三方庫來實現(xiàn)測試替身解決 HTTP 外部依賴。

并且分別介紹了使用 setupTestUser + defer cleanup() 以及 TestMain 兩種形式,來做測試準備和清理工作。二者作用于不同粒度,需要根據(jù)測試需要進行選擇。

本文完整代碼示例:blog-go-example/test/http at main · jianghushinian/blog-go-example · GitHub

以上就是在Go語言單元測試中解決HTTP網(wǎng)絡(luò)依賴問題的詳細內(nèi)容,更多關(guān)于Go HTTP網(wǎng)絡(luò)依賴的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言中循環(huán)Loop的用法介紹

    Go語言中循環(huán)Loop的用法介紹

    這篇文章介紹了Go語言中循環(huán)Loop的用法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • Go語言RPC Authorization進行簡單ip安全驗證的方法

    Go語言RPC Authorization進行簡單ip安全驗證的方法

    這篇文章主要介紹了Go語言RPC Authorization進行簡單ip安全驗證的方法,實例分析了Go語言進行ip驗證的技巧,需要的朋友可以參考下
    2015-03-03
  • 詳解如何在Go中如何編寫出可測試的代碼

    詳解如何在Go中如何編寫出可測試的代碼

    在編寫測試代碼之前,還有一個很重要的點,容易被忽略,就是什么樣的代碼是可測試的代碼,所以本文就來聊一聊在?Go?中如何寫出可測試的代碼吧
    2023-08-08
  • 詳解golang各種類型是如何進行比較的

    詳解golang各種類型是如何進行比較的

    在日常開發(fā)中,比較操作是最常用的基本操作之一,可以用來判斷變量之間是否相等或者對應(yīng)的大小關(guān)系,比較操作對于排序、查找和集合數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)至關(guān)重要,本文將深入解析golang各種類型是如何進行比較的,需要的朋友可以參考下
    2024-01-01
  • Go語言中命令行參數(shù)解析工具pflag的使用指南

    Go語言中命令行參數(shù)解析工具pflag的使用指南

    在使用?Go?進行開發(fā)的過程中,命令行參數(shù)解析是我們經(jīng)常遇到的需求,于是?Go?社區(qū)中出現(xiàn)了一個叫?pflag?的第三方包,功能更加全面且足夠強大,下面我們就來看看它的具體使用吧
    2024-11-11
  • Go 如何基于IP限制HTTP訪問頻率的方法實現(xiàn)

    Go 如何基于IP限制HTTP訪問頻率的方法實現(xiàn)

    這篇文章主要介紹了Go 如何基于IP限制HTTP訪問頻率的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Golang中自定義json序列化時間格式的示例代碼

    Golang中自定義json序列化時間格式的示例代碼

    Go語言作為一個由Google開發(fā),號稱互聯(lián)網(wǎng)的C語言的語言,自然也對JSON格式支持很好,下面這篇文章主要介紹了關(guān)于Golang中自定義json序列化時間格式的相關(guān)內(nèi)容,下面話不多說了,來一起看看詳細的介紹吧
    2024-08-08
  • 深入了解Golang的map增量擴容

    深入了解Golang的map增量擴容

    這篇文章主要介紹了深入了解Golang的map增量擴容,擴容的主要目的是為了縮短map容器的響應(yīng)時間。增量擴容的本質(zhì)其實就是將總的擴容時間分攤到了每一次hash操作上,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-06-06
  • 一文教你打造一個簡易的Golang日志庫

    一文教你打造一個簡易的Golang日志庫

    這篇文章主要為大家詳細介紹了如何使用不超過130行的代碼,通過一系列g(shù)olang的特性,來打造一個簡易的golang日志庫,感興趣的小伙伴可以了解一下
    2023-06-06
  • GoLand利用plantuml生成UML類圖

    GoLand利用plantuml生成UML類圖

    本文主要介紹了GoLand利用plantuml生成UML類圖,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06

最新評論

黄梅县| 繁峙县| 嘉善县| 蛟河市| 许昌县| 榕江县| 新竹县| 天津市| 绥中县| 南郑县| 华容县| 平潭县| 金寨县| 留坝县| 霞浦县| 皮山县| 沾益县| 池州市| 昌吉市| 石狮市| 泸水县| 临澧县| 嘉定区| 武强县| 井冈山市| 内乡县| 英吉沙县| 馆陶县| 肥城市| 东方市| 城固县| 阜南县| 饶河县| 中阳县| 舞阳县| 长寿区| 屯昌县| 甘孜县| 夹江县| 昭平县| 彰化县|