Golang单元测试第一章——网络测试

参考博客:Go单测从零到溜系列1—网络测试 | 李文周的博客 (liwenzhou.com)

单元测试到底是什么?我理解的是,让缺少的东西先用模拟出来的(假的)代替,先凑合着用

原文中有这样一句话:无论我们的代码是作为server端对外提供服务或者还是我们依赖别人提供的网络服务(调用别人提供的API接口)的场景,我们通常都不想在测试过程中真正的建立网络连接。本文就专门介绍如何在上述两种场景下mock网络测试。

这句话有点似懂非懂,但是隐隐约约能够感受到,如果我们在goWeb后端代码,使用apifox或者postman测试的时候,网络测试能够帮助我们节省时间,提高效率。抱着这个想法,我开始了学习单元测试。

接着往下看

httptest

这个库是用来测试http请求,现在用gin框架开发的接口,应该都是http请求(网络没有详细学过,此处不是太懂),我们使用httptest这个Go标准库提供的包,可以提高测试效率。

httptest编写测试函数,被测试函数Handler (func c*gin.Context)

编写完测试函数我们能干什么呢?

博客原话:这种情况下我们就可以使用httptest这个工具mock一个HTTP请求和响应记录器,让我们的server端接收并处理我们mock的HTTP请求,同时使用响应记录器来记录server端返回的响应内容。

我的理解是:我们之前测试都是使用的apifox来进行测试,需要先把项目跑起来,然后打开apifox,进行封装请求,然后点击发送按钮发送请求,然后再返回代码进行debug.... 现在的话,我们可以直接跳过apifox,所有的东西都在单元测试代码中完成,这样的话,就省去了使用apifox所花费的时间。

继续往下看:

有一个例子,代码如下

http Server端

// gin.go
package httptest_demo

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

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

单元测试

package httptest_demo

import (
    "encoding/json"
    "github.com/stretchr/testify/assert"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

func Test_helloHandler(t *testing.T) {
    tests := []struct {
        name   string `json:"name"`
        param  string `json:"param"`
        expect string `json:"expect"`
    }{
        {"base case", `{"name":"liwenzhou"}`, `hello liwenzhou`},
        {"bad case", "", "we need a name"},
    }
    r := SetupRouter()
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            //mock一个http请求
            req := httptest.NewRequest("POST", "/hello", strings.NewReader(tt.param))
            //mock一个响应器记录
            w := httptest.NewRecorder()
            //让server端口处理mock请求并记录返回的响应内容
            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, tt.expect, resp["msg"])
        })
    }
}

最初看着上面的代码,最初不太理解,但是自己跟着上面敲一了一遍,然后又debug运行了一遍,才发现,确实就是和上面说的功效一样,能够让我们跳过apifox的步骤,直接在后端就做好了。

gock

上面的单元测试针对的是我们是服务端,别人从我们的服务端拿数据。但是现在有一种新的情景,我们需要去其他的客户端拿数据,也就是说,我们需要去访问其他第三方网站。

这个时候,我们使用gock可以对外部的API进行mock,可以指定参数并返回约定好的响应内容。

gock主要是用来解决:不想在测试过程中真正去发送请求;依赖的外部接口还没有开发完成。

看完这来,还是不太理解,如果我们mock了,那不就是等于没有访问第三方的接口,那我们想要的数据肯定是拿到了。

纸上得来终觉浅,绝知此事要躬行。敲一遍代码亲自体会一下

代码如下:

业务逻辑代码,依赖外部API http://your-api.com/post提供的数据

//api.go
package httptest_demo

import (
    "bytes"
    "encoding/json"
    "io/ioutil"
    "net/http"
)

type ReqParam struct {
    X int `json:"x"`
}
type Result struct {
    Value int `json:"value"`
}

func GetResultByAPI(x,y int) int {
    p := &ReqParam{X: x}
    b, _ := json.Marshal(p)
    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 res Result
    err = json.Unmarshal(body, &res)
    if err != nil {
        return -1
    }
    return res.Value + y
}

单元测试代码,使用gock对外部api进行mock,指定参数返回约定好的响应内容。

// api_test.go
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被触发
}

刚开始看的时候,不是很理解代码的意思,但是后面才发现

gock.New("http://your-api.com").
        Post("/post").
        MatchType("json").
        JSON(map[string]int{"x": 2}).
        Reply(200).
        JSON(map[string]int{"value": 200})

gock.New是关键,他能把尚未开发完成的接口(http://your-api.com)给mock出来,也就是说,即使没有http://your-api.com 接口,我们依然可以拿到数据,只不过数据是假的,是我们自己定出来的。

你可能感兴趣的:(Golang单元测试,golang,后端,golang,开发语言,单元测试)