Day25 Gin with API Test

What is API Test?

我们可以把它想成Unit Test单元测试的一种,不过它所涵盖的最好集合不像以往的UnitTest可能以Function为主,而是Endpoint

透过API Test我们能够在开发与修改程序码时,确保被修改到部分程序码的API能够如预期的运作,那这边我们会以官方提供的httptest来实作。

https://ithelp.ithome.com.tw/upload/images/20211010/20129737oKVtf3CBHN.png

API Test with httptest

首先我们将main当中的router部分给分离出来

main.go

package main

import (
	"fmt"
	"github.com/chenyahui/gin-cache/persist"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"github.com/joho/godotenv"
	ginSwagger "github.com/swaggo/gin-swagger"
	"github.com/swaggo/gin-swagger/swaggerFiles"
	"ironman-2021/app/config"
	"ironman-2021/app/dao"
	"ironman-2021/app/middleware"
	"ironman-2021/app/model"
	"net/http"
	"os"
)

// @title Gin swagger
// @version 1.0
// @description Gin swagger

// @contact.name Flynn Sun

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8080
// schemes http
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
func main() {
	envErr := godotenv.Load()
	if envErr != nil {
		panic(envErr)
	}

	port := os.Getenv("PORT")
	server := SetRouter()
	err := server.Run(":" + port)
	if err != nil {
		panic(err)
	}
}

func SetRouter() *gin.Engine {
	envErr := godotenv.Load()
	if envErr != nil {
		panic(envErr)
	}

	dbConfig := os.Getenv("DB_CONFIG")
	db, ormErr := dao.Initialize(dbConfig)
	if ormErr != nil {
		panic(ormErr)
	}
	migrateErr := db.AutoMigrate(&model.User{})
	if migrateErr != nil {
		return nil
	}

	server := gin.Default()
	server.Use(middleware.CORSMiddleware())
	server.GET("/hc", func(c *gin.Context) {
		c.String(http.StatusOK, fmt.Sprintf("Health Check"))
	})
	redisStore := persist.NewRedisStore(redis.NewClient(&redis.Options{
		Network: "tcp",
		Addr:    "redis:6379",
		DB:      0,
	}))
	config.RouteUsers(server, redisStore)
	url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
	server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
	return server
}

接着则在专案的根目录加上一个main_test.go的档案

main_test.go

package main

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

func Test_setupRouter(t *testing.T) {
	router := SetRouter()

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/hc", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Contains(t, w.Body.String(), "Health Check")
}
  • 首先一样创建一个与main相同的router
  • 接着Create 一个ResponseRecorder的物件
  • 再来则将Request /hc的Response给conform到ResponseRecorder
  • 然後比较Response Code、Response Text等内容是否符合预期

Run in Container

  • 首先进入ironman-2021这个container
docker exec -it ironman-2021 bash                                                                 4280  16:27:57 
root@88c2c60ca49b:/usr/local/go/src/ironman-2021# ls
  • 接着输入go test -v的command执行单元测试
go test -v
=== RUN   Test_setupRouter
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[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] GET    /hc                       --> ironman-2021.SetRouter.func1 (4 handlers)
[GIN-debug] POST   /v1/users/                --> ironman-2021/app/controller.UsersController.CreateUser-fm (4 handlers)
[GIN-debug] GET    /v1/users/:id             --> ironman-2021/app/controller.UsersController.GetUser-fm (6 handlers)
[GIN-debug] POST   /v1/users/login           --> ironman-2021/app/controller.UsersController.AuthHandler-fm (4 handlers)
[GIN-debug] GET    /swagger/*any             --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (4 handlers)
[GIN] 2021/10/08 - 08:29:40 | 200 |       107.8µs |                 | GET      "/hc"
--- PASS: Test_setupRouter (0.06s)
PASS
ok      ironman-2021    0.100s

那从这我们就可以看到他执行单元测试的结果与耗费时间等资料,用以确认是否程序码如预期地运作!

More Test!

再来我们就可以依序写更多的Test Code,从Create a new userLogin with an existing user

package main

import (
	"bytes"
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"testing"
)

func Test_setupRouter(t *testing.T) {
	router := SetRouter()

	w := httptest.NewRecorder()
	req1, _ := http.NewRequest("GET", "/hc", nil)
	router.ServeHTTP(w, req1)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Contains(t, w.Body.String(), "Health Check")

	var jsonStr1 = []byte(`{"account":"account","password":"password", "email":"[email protected]"}`)
	req2, _ := http.NewRequest("POST", "/v1/users/", bytes.NewBuffer(jsonStr1))

	router.ServeHTTP(w, req2)
	assert.Equal(t, http.StatusOK, w.Code)

	var jsonStr2 = []byte(`{"account":"account","password":"password"`)
	req3, _:= http.NewRequest("POST", "/v1/users/login/", bytes.NewBuffer(jsonStr2))
	router.ServeHTTP(w, req3)
	assert.Equal(t, http.StatusOK, w.Code)
}

Summary

这章节主要介绍如何使用官方的httptest来写Http Test,希望能降低修改程序码时出错的概率!

相关的程序码一样会放在下方连结。

https://github.com/Neskem/Ironman-2021/tree/Day-25


<<:  仓库进出货管理

>>:  心得

[Day 15] 资料产品生命周期管理-预测模型

尽管都是模型,但预测模型目的在於预测未来,所以开发方式也会和描述型模型有所差异。 Initiatio...

#17 JS: loop - Part 2

To fully understand how loop works, I try to break...

Day 13 wireframe 黑白线稿 ( 互动式地图设计 + footer )

来找设计师一起 side project,前後端 / UIUX 皆可ㄛ。配对单连结: https:...

【Day 7】GIT版本还原及回归

说明:分支的版本还原 还原前1个版本指令 git reset HEAD^ ^可以决定要还原到哪一版,...

Day5 随机森林法(Random Forest)

随机森林法是什麽? 讲人话就是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数...