2

Go单测从零到溜系列——1.网络测试

 3 years ago
source link: https://www.liwenzhou.com/posts/Go/golang-unit-test-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

这是Go语言单元测试从零到溜系列教程的第1篇,介绍了如何使用httptest和gock工具进行网络测试。

Go语言单元测试从零到溜系列共7篇,本文是第1篇,介绍了如何使用httptest和gock工具进行网络测试。

httptest

在Web开发场景下的单元测试,如果涉及到HTTP请求话使用Go标准库 net/http/httptest 进行测试更为高效。

以常见的gin框架为例,我们现在搭建了一个server端,提供了一个helloHandler函数:

// gin.go

// Param 请求参数
type Param struct {
	Name string `json:"name"`
}

// helloHandler /hello请求处理函数
func helloHandler(c *gin.Context) {
	var p Param
	if err := c.ShouldBindJSON(&p); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"msg": "we need a name",
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"msg": fmt.Sprintf("hello %s", p.Name),
	})
}

// SetupRouter 路由
func SetupRouter() *gin.Engine {
	router := gin.Default()
	router.POST("/hello", helloHandler)
	return router
}

这个时候要为helloHandler编写单元测试,就可以使用httptest创建请求和响应。

// gin_test.go

func Test_helloHandler(t *testing.T) {
	tt := []struct {
		name   string
		param  string
		expect string
	}{
		{"base case", `{"name": "liwenzhou"}`, "hello liwenzhou"},
		{"bad case", "", "we need a name"},
	}

	r := SetupRouter()

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			// 构建请求
			req := httptest.NewRequest(
				"POST",
				"/hello",
				strings.NewReader(tc.param),
			)

			// 获取响应
			w := httptest.NewRecorder()
			r.ServeHTTP(w, req)
			assert.Equal(t, http.StatusOK, w.Code)

			// 解析并检验响应内容
			var resp map[string]string
			err := json.Unmarshal([]byte(w.Body.String()), &resp)
			assert.Nil(t, err)
			assert.Equal(t, tc.expect, resp["msg"])
		})
	}
}

如果是在代码中请求外部API的场景,比如我们项目中通过API调用其他服务。

// api.go

// 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。

这里推荐使用gock这个库。

go get -u gopkg.in/h2non/gock.v1
// api_test.go

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被触发
}

在Web开发场景下为代码编写单元测试时如何处理外部依赖是最常见的问题,本文介绍了如何使用httptest和gock工具mock相关依赖。 在下一篇中,我们将更进一步,详细介绍针对依赖MySQL和Redis的场景如何编写单元测试。


扫码关注微信公众号


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK