前面的话
本篇针对 gin 中的 GET 与 POST 路由方法。
GET: 通过URL获取参数POST: 通过PostForm获取参数
路由配置
下面对于 GET 与 POST 路由定义如下
func GetRouter() *gin.Engine {
r := gin.Default()
// base interfaces
r.GET("/testget", func(c *gin.Context) {
var a, b int
var err error
if a, err = strconv.Atoi(c.Query("a")); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
if b, err = strconv.Atoi(c.Query("b")); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
c.JSON(http.StatusOK, map[string]int{"res": a + b})
})
r.POST("/testpost", func(c *gin.Context) {
var a, b int
var err error
if a, err = strconv.Atoi(c.PostForm("a")); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
if b, err = strconv.Atoi(c.PostForm("b")); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
f, err := c.FormFile("filename") // 上传时文件的参数名
if err = c.SaveUploadedFile(f, "filepath"); err != nil {
// 文件保存路径
c.JSON(http.StatusBadRequest, err.Error())
}
c.JSON(http.StatusOK, gin.H{"res": a + b})
})
return r
}
GET 方法测试
func TestGetSuccess(t *testing.T) {
r := gintest.GetRouter()
data := url.Values{}
data.Add("a", strconv.Itoa(1))
data.Add("b", strconv.Itoa(2))
req, err := http.NewRequest(http.MethodGet, "/testget", nil)
if err != nil {
t.Fatalf("构建请求失败: %s", err.Error())
}
req.URL.RawQuery = data.Encode()
record := httptest.NewRecorder()
r.ServeHTTP(record, req)
res := record.Result()
if res.StatusCode != http.StatusOK {
t.Fatalf("状态码不符合预期: %d", res.StatusCode)
}
buf := make([]byte, 1024)
n, err := res.Body.Read(buf)
if err != nil {
t.Fatalf("读取错误: %s", err.Error())
}
body := map[string]int{}
if err = json.Unmarshal(buf[:n], &body); err != nil {
t.Fatalf("解析失败: %s", err.Error())
}
if body["res"] != 3 {
t.Fatalf("返回结果不符合预期: %d", body["res"])
}
}
func TestGetFail(t *testing.T) {
r := gintest.GetRouter()
data := url.Values{}
data.Add("a", strconv.Itoa(1))
data.Add("b", "test input error")
req, err := http.NewRequest(http.MethodGet, "/testget", nil)
if err != nil {
t.Fatalf("Build request failed, err: %v", err)
}
req.URL.RawQuery = data.Encode()
record := httptest.NewRecorder()
r.ServeHTTP(record, req)
res := record.Result()
if res.StatusCode != http.StatusBadRequest {
t.Fatalf("状态码不符合预期: %d", res.StatusCode)
}
buf := make([]byte, 1024)
n, err := res.Body.Read(buf)
if err != nil {
t.Fatalf("读取错误: %s", err.Error())
}
var errMsg string
if err = json.Unmarshal(buf[:n], &errMsg); err != nil {
t.Fatalf("解析失败: %s", err.Error())
}
if errMsg != "strconv.Atoi: parsing "test input error": invalid syntax" {
t.Fatalf("返回结果不符合预期: %s", errMsg)
}
}
r是需要被测试的路由data构建请求参数req为http请求体res为返回HTTP通过r.ServeHTTP方法获得请求结果buf需要足够大的空间来容纳返回结果,以供接下来的解析body/errMsg是返回体,可以自定义结构体来进行解析。- 通过校验以上所有参数即可完成简单的使用
go test测试gin - 对于更复杂的错误判断逻辑应该实现更多的
Test函数
POST 方法测试
func TestPostSuccess(t *testing.T) {
testUploadFile := "./go.mod"
r := gintest.GetRouter()
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("a", "1")
_ = writer.WriteField("b", "2")
var (
file *os.File
part io.Writer
err error
)
if file, err = os.Open(testUploadFile); err != nil {
t.Fatalf("打开文件失败: %s", err.Error())
}
defer file.Close()
if part, err = writer.CreateFormFile("filename", filepath.Base(testUploadFile)); err != nil {
t.Fatalf("创建文件参数失败: %s", err.Error())
}
if _, err = io.Copy(part, file); err != nil {
t.Fatalf("文件参数数据拷贝失败: %s", err.Error())
}
if err = writer.Close(); err != nil {
t.Fatalf("参数写入 PostForm 失败: %s", err.Error())
}
req, err := http.NewRequest(http.MethodPost, "/testpost", payload)
if err != nil {
t.Fatalf("构建请求体失败: %s", err.Error())
}
req.Header.Set("Content-Type", writer.FormDataContentType())
record := httptest.NewRecorder()
r.ServeHTTP(record, req)
res := record.Result()
if res.StatusCode != http.StatusOK {
t.Fatalf("状态码不符合预期: %d", res.StatusCode)
}
buf := make([]byte, 1024)
n, err := res.Body.Read(buf)
if err != nil {
t.Fatalf("读取错误: %s", err.Error())
}
body := map[string]int{}
if err = json.Unmarshal(buf[:n], &body); err != nil {
t.Fatalf("解析失败: %s", err.Error())
}
if body["res"] != 3 {
t.Fatalf("返回结果不符合预期: %d", body["res"])
}
}
func TestPostFail(t *testing.T) {
testUploadFile := "./go.mod"
r := gintest.GetRouter()
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("a", "1")
_ = writer.WriteField("b", "test input error")
var (
file *os.File
part io.Writer
err error
)
if file, err = os.Open(testUploadFile); err != nil {
t.Fatalf("打开文件失败: %s", err.Error())
}
defer file.Close()
if part, err = writer.CreateFormFile("filename", filepath.Base(testUploadFile)); err != nil {
t.Fatalf("创建文件参数失败: %s", err.Error())
}
if _, err = io.Copy(part, file); err != nil {
t.Fatalf("文件参数数据拷贝失败: %s", err.Error())
}
if err = writer.Close(); err != nil {
t.Fatalf("参数写入 PostForm 失败: %s", err.Error())
}
req, err := http.NewRequest(http.MethodPost, "/testpost", payload)
if err != nil {
t.Fatalf("构建请求体失败: %s", err.Error())
}
req.Header.Set("Content-Type", writer.FormDataContentType())
record := httptest.NewRecorder()
r.ServeHTTP(record, req)
res := record.Result()
if res.StatusCode != http.StatusBadRequest {
t.Fatalf("状态码不符合预期: %d", res.StatusCode)
}
buf := make([]byte, 1024)
n, err := res.Body.Read(buf)
if err != nil {
t.Fatalf("读取错误: %s", err.Error())
}
var errMsg string
if err = json.Unmarshal(buf[:n], &errMsg); err != nil {
t.Fatalf("解析失败: %s", err.Error())
}
if errMsg != "strconv.Atoi: parsing "test input error": invalid syntax" {
t.Fatalf("返回结果不符合预期: %s", errMsg)
}
}
- 与
GET方法基本一致 - 不同点在于参数的构建与写入方法
POST方法还要在请求头中写入请求体的格式POST可以上传文件这样的其他格式参数POST方法的测试小技巧: 因为POST方法有各种输入格式,这里利用Postman可以快速得到请求现有服务器的方法,只要将服务器换成本地的gin路由器就可以在go test中进行测试。
注意把
client 与 res 的获取方式换掉即可。