古老的榕树

用 Go 开发终端接口服务--测试已写好的接口

潘军杰 发表于 2019-05-14 18:30 阅读(4863) 评论(1) 赞(1)
项目的测试工作相当重要,它直接影响到项目的质量。通常一个项目,虽然外围测试团队会进行高强度、多维度的测试,但作为开发者的我们,在测试团队正式测试之前,自己编写一些测试案例,输入一些常规的数据,测试接口是否返回正确的数据,这种工作是非常有必要的,开发者不能没有经过测试,就直接扔给测试团队。

编写测试案例,总体思路是:先分别定义一个请求结构体和响应结构体,再定义一个发送 POST 请求的函数,函数接收请求结构体实例,返回响应结构体实例,最后通过 html 页面罗列所有的接口链接,测试者点击链接,链接就调用发送 POST 请求的函数,把函数返回的请求数据和响应数据直接显示出来,验证测试结果是否正确。

我们新建一个 chapter01/src/test 测试包,定义结构体和辅助函数:

*代码清单 - 测试实体结构体代码片段*

package test

import "time"

// TestCaseReq 测试案例请求结构体
type TestCaseReq struct {
	Name   string
	URL    string
	Params map[string]interface{}
}

// TestCaseResp 测试案例请响应结构体
type TestCaseResp struct {
	Name     string
	URL      string
	ReqJSON  string
	RespJSON string
	TimeMs   time.Duration
}
请求结构体,包括一个测试的接口名、URL 测试地址、传递给接口的参数(字典方式);响应结构体同样是接口名、URL 测试地址、请求参数 JSON 字符串(是字段转成了 JSON 字符串)、响应参数 JSON 字符串、耗费时长。请求结构体都是 POST 请求接口所需要的东西,响应结构体是我们想在 html 页面看到的东西。

*代码清单 - 测试公共函数代码片段*

package test

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

	"chapter01/src/common"
)

// PostTestCaseDataToServer 发送 POST 测试案例请求的函数
func PostTestCaseDataToServer(testCaseReq TestCaseReq) *TestCaseResp {
	start := time.Now()

	bytParams, err := json.Marshal(testCaseReq.Params)
	if err != nil {
		common.ShowErr(err)
		return nil
	}

	body := bytes.NewBuffer(bytParams)
	req, err := http.NewRequest("POST", testCaseReq.URL, body)
	if err != nil {
		common.ShowErr(err)
		return nil
	}
	req.Header.Set("Content-Type", applicationJSONCharSet)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		common.ShowErr(err)
		return nil
	}
	defer resp.Body.Close()

	respJSON, _ := ioutil.ReadAll(resp.Body)

	testCaseResp := TestCaseResp{
		Name:     testCaseReq.Name,
		URL:      testCaseReq.URL,
		ReqJSON:  string(bytParams),
		RespJSON: string(respJSON),
		TimeMs:   time.Since(start),
	}

	return &testCaseResp
}
辅助函数实际上就是发送 POST 请求函数,把请求 JSON 字符串 POST 给我们的服务,服务接口再返回响应 JSON 数据。

这些都是程序化的东西,相对固定,有迹可循,也是必不可少的,然而案例测试数据才是最关键的,它是变化的,需要根据不同的接口,初始化请求结构体实例,放入字典里,给辅助函数调用。
*代码清单 - 案例测试数据片段*
package test

import (
	"chapter01/src/model"
	"chapter01/src/util"
)

const (
	testDomain             = "http://localhost:3000"
	applicationJSONCharSet = "application/json;charset=utf-8"
)

// ALLTestCaseReqData 全部测试案例请求数据
func ALLTestCaseReqData() map[string]TestCaseReq {
	allTestCaseReq := map[string]TestCaseReq{}

	allTestCaseReq["pathProductList"] = TestCaseReq{
		Name: "产品列表",
		URL:  testDomain + "/api/v1/product/list",
		Params: map[string]interface{}{
			"category": 1,
			"start":    1,
			"end":      10,
		},
	}

	allTestCaseReq["pathProductDetail"] = TestCaseReq{
		Name: "产品详情页",
		URL:  testDomain + "/api/v1/product/detail",
		Params: map[string]interface{}{
			"productID": 1,
		},
	}

	allTestCaseReq["pathProductSearch"] = TestCaseReq{
		Name: "产品搜索",
		URL:  testDomain + "/api/v1/product/search",
		Params: map[string]interface{}{
			"category": 1,
			"key":      "语言",
			"start":    1,
			"end":      10,
		},
	}

	allTestCaseReq["pathProductAdd"] = TestCaseReq{
		Name: "新增一个产品",
		URL:  testDomain + "/api/v1/product/add",
		Params: map[string]interface{}{
			"category": 1,
			"name":     "Go",
			"intro":    "Go 语言的圣经书籍,年度畅销书籍",
			"price":    56.98,
			"photoEdit": []model.PhotoArgs{model.PhotoArgs{
				Path: "/product/154192547123856140062_b.png",
				Seq:  1,
			}, model.PhotoArgs{
				Path: "/product/154192547123856140062_b.png",
				Seq:  2,
			}, model.PhotoArgs{
				Path: "/product/154192547123856140062_b.png",
				Seq:  3}},
		},
	}

	allTestCaseReq["pathProductModify"] = TestCaseReq{
		Name: "修改一个产品",
		URL:  testDomain + "/api/v1/product/modify",
		Params: map[string]interface{}{
			"productID": 3,
			"category":  1,
			"name":      "Go",
			"intro":     "Go 语言的圣经书籍,年度畅销书籍666",
			"price":     56.98,
			"photoEdit": []model.PhotoArgs{model.PhotoArgs{
				ID:   1,
				Path: "/product/154192547123856140062_b.png",
				Seq:  1,
			}, model.PhotoArgs{
				ID:   2,
				Path: "/product/154192547123856140062_b.png",
				Seq:  2,
			}, model.PhotoArgs{
				ID:   3,
				Path: "/product/154192547123856140062_b.png",
				Seq:  3}},
		},
	}

	allTestCaseReq["pathProductDelete"] = TestCaseReq{
		Name: "删除一个产品",
		URL:  testDomain + "/api/v1/product/delete",
		Params: map[string]interface{}{
			"productID": 3,
		},
	}

	allTestCaseReq["pathProductPhotoUpload"] = TestCaseReq{
		Name: "产品图片上传",
		URL:  testDomain + "/api/v1/product/photo/upload",
		Params: map[string]interface{}{
			"file":    util.PathToBytes("D:\\t.png"),
			"fileExt": "png",
			"seq":     1,
		},
	}

	return allTestCaseReq
}
案例测试数据随着接口的增多,也会相应增加,做法上没有技术性的难点,我们只需要熟悉业务性的东西,自然就可以按需制造数据,用于发送 POST 请求进行测试。

案例测试数据其实就是一个大字典,是键值对结构的,key 是接口的名称标识,value 是 TestCaseReq 实例,我们大部分的精力都放在 TestCaseReq 实例数据上了。

最后我通过 HTML 方式,把大字典的 key 和 value 循环绑定到链接上 ,相当于罗列了所有测试接口的请求链接,给用户点击链接,就把 value (TestCaseReq 实例)传递给辅助函数 PostTestCaseDataToServer,得到一个 响应结构体的实例(TestCaseResp 实例),再把实例的所有字段的数据显示到另外的 html 结果页面上。渲染 HTML 由 controller 控制层来完成:
*代码清单 - 测试控制器代码片段*
package controller

import (
	"net/http"

	"chapter01/src/test"
	)

// RunTestIndex 循环罗列所有的接口链接
func RunTestIndex(w http.ResponseWriter, req *http.Request) {
	ctx := map[string]interface{}{}
	ctx["allTestCaseReq"] = test.ALLTestCaseReqData()
	r.HTML(w, http.StatusOK, "run_api_test_index", ctx)
	return
}

// RunTestResult 显示 POST 请求后的结果页面
func RunTestResult(w http.ResponseWriter, req *http.Request) {
	key := req.FormValue("key")
	testCaseReqData := test.ALLTestCaseReqData()[key]
	testCaseResp := test.PostTestCaseDataToServer(testCaseReqData)
	ctx := map[string]interface{}{}
	ctx["testCaseResp"] = testCaseResp
	r.HTML(w, http.StatusOK, "run_api_test_result", ctx)
	return
}
文件夹 template 中的两个 HTML 页面,一个是循环罗列所有的接口链接(run_api_test_index.html),一个是显示 POST 请求后返回的显示结果页面(run_api_test_result.html)

*代码清单 - run_api_test_index.html 循环罗列所有的接口链接代码*
<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>接口测试首页</title>
</head>
<body>
<h3>接口测试链接</h3>
    <ol>
        {{range $key,$val:= .allTestCaseReq}}
        <li><a target="_blank" href="/test/result?key={{$key}}">{{$val.Name}} {{$val.URL}}</a></li>
        {{end}}
    </ol>
</body>
</html>


代码清单 - run_api_test_result.html 显示结果页面代码

<!DOCTYPE html>
<html>

<head>
    <title>{{.testCaseResp.Name}} - 测试接口</title>
    <style type="text/css">
        body,table{font-size:12px}h1{font-size:1.4em;color:#2d2d2d}table{table-layout:fixed;empty-cells:show;border-collapse:collapse;width:100%}.table{color:#444;border-top:1px solid #ccc}.table th{background-repeat:repeat-x}.table td,.table th{border-bottom:1px solid #ccc;padding:0.8em 0.6em;word-break:break-all}.table tr.alter{background-color:#eee}.table tr .title{width:130px;color:#555}.table tr .cont{color:#000}</style>
</head>

<body>
<h1>请求的接口:{{.testCaseResp.URL}}</h1>
<table class="table">
    <tr class="alter">
        <td class="title">请求参数:</td>
        <td class="cont">{{.testCaseResp.ReqJSON}}</td>
    </tr>
    <tr>
        <td class="title">请求类型:</td>
        <td class="cont">POST</td>
    </tr>
    <tr class="alter">
        <td class="title">响应内容:</td>
        <td class="cont">{{.testCaseResp.RespJSON}}</td>
    </tr>
    <tr>
        <td class="title">总计耗时:</td>
        <td class="cont">{{.testCaseResp.TimeMs}}</td>
    </tr>
</table>
</body>

</html>


小结
本章节核心内容是案例测试数据的构建,其他实体结构,公共函数,HTML 这些代码相对非常固定,几乎不需要变动,所以我们要把主要精力放在数据构建上,多方面模拟不同的情况来测试接口。



《用 Go 开发终端接口服务》 目录


1 条网友评论

1 楼: 潘军杰 (博主) 发表于 2021-08-20 11:27:55   回复 TA

建议使用 postman 进行测试接口
称呼*
邮箱*
内容*
验证码*
验证码 看不清换张