单元测试:网络测试,sql测试 | 青训营笔记

126 阅读2分钟

这是我参与[第三届青训营-后端场]笔记创作活动的第4篇笔记

网络测试

HTTP请求测试,使用Go标准库net/http/httptest进行测试。

以gin框架为例,先简单定义一个handle接口。

package test

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "net/http"
)

type Request struct {
   Name string `json:"name"`
}

func Hello(c *gin.Context) {
   req := Request{}
   if err := c.ShouldBindJSON(&req); err != nil {
      c.JSON(http.StatusOK, gin.H{
         "msg": "Bye Bye",
      })
      return
   }
   c.JSON(http.StatusOK, gin.H{
      "msg": fmt.Sprintf("Hello %s", req.Name),
   })
}
func setUpRouter() *gin.Engine {
   r := gin.New()
   r.Handle("POST", "/hello", Hello)
   return r
}

使用httptest这个工具来mock一个http请求:httptest.NewRequest()和mock一个记录器记录响应:httptest.NewRecorder(),作为保存服务端响应的记录器。

package test

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

func TestHTTP(t *testing.T) {
   cases := []struct {
      name   string
      param  string
      expect string
   }{
      {"success", `{"name":"haogee"}`, "Hello haogee"},
      {"fail", "", "Bye Bye"},
   }
   r := setUpRouter()
   for _, c := range cases {
      t.Run(c.name, func(t *testing.T) {
         req := httptest.NewRequest(
            "POST",
            "/hello",
            strings.NewReader(c.param),
         )
         w := httptest.NewRecorder()
        //发起请求,并将响应写入recorder中。
         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, c.expect, resp["msg"])
      })
   }
}

执行单元测试的结果

$ go test -v -run TestHTTP
=== RUN   TestHTTP
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /hello                    --> demo/test.Hello (1 handlers)
=== RUN   TestHTTP/success
2022/05/15 23:18:10 <nil>
=== RUN   TestHTTP/fail
2022/05/15 23:18:10 <nil>
--- PASS: TestHTTP (0.03s)
    --- PASS: TestHTTP/success (0.03s)
    --- PASS: TestHTTP/fail (0.00s)
PASS
ok      demo/test       0.561s

根据这个样例,定义一个测试的方案就是:使用httptest mock请求和recorder,执行服务,解析响应数据,再使用assert测试工具进行测试结果分析。

sql测试

  • 对数据库的单元测试可用使用go-sqlmock实现 先进行mock初始化
var (
   mock sqlmock.Sqlmock
   err  error
   db   *sql.DB
)

// TestMain是在当前package下,最先运行的一个函数,常用于初始化
func TestMain(m *testing.M) {
   //把匹配器设置成相等匹配器,不设置默认使用正则匹配
   db, mock, err = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
   if err != nil {
      panic(err)
   }
   model.DB, err = gorm.Open(mysql.New(mysql.Config{
      SkipInitializeWithVersion: true,
      Conn:                      db,
   }), &gorm.Config{})
   if err != nil {
      panic(err)
   }

   // m.Run 是调用包下面各个Test函数的入口
   os.Exit(m.Run())
}

举个测试insert的例子

func TestInsert(t *testing.T) {
   user := &model.User{
      Name:     "haogee",
      Password: "123456",
   }
   user.CreatedAt = time.Now()
   user.UpdatedAt = time.Now()
   mock.ExpectBegin()
   mock.ExpectExec("INSERT INTO `users` (`created_at`,`updated_at`,`deleted_at`,`name`,`password`) VALUES (?,?,?,?,?)").
      WithArgs(user.CreatedAt, user.UpdatedAt, nil, user.Name, user.Password).
      WillReturnResult(sqlmock.NewResult(1, 1))
   mock.ExpectCommit()
   err := model.NewUserManagerRepository().Insert(user)
   assert.Nil(t, err)
}