golang工具:一.测试(单元测试,基准测试,API接口测试)
在开发过程中,为了尽早发现代码中的缺陷和不住,我们经常要写单元测试。从而更快速的发现问题并解决问题,单元测试的效率也比传统手测更高很多。golang的单元测试很简单,先看一个例子
单元测试
用来检测一些程序逻辑的正确性。
简单示例
待测函数
package common
func Add(a,b int) int {
return a + b
}
测试函数
package common
import "testing"
func TestAdd(t *testing.T) {
var (
a = 1
b = 5
expect = 6
)
reality := Add(a, b)
if reality != expect {
t.Errorf("DestroyList error: expect %v,but got %v", expect, reality)
}
}
运行测试命令
go test -v -run TestAdd ( 这里也可以用:go test -v . 运行该包下所有的测试函数, -run 的参数实际上是一个正则表达式。-v 是显示测试命令的名称和执行时间。)
待测函数是一个加法运算,测试函数分别输入两个值然后把运行的结果和预期比对,如果和预期不符,说明我们的待测函数可能有问题,这是输出Errorf。这样就可以测试我们的待测函数。我们还可以用多组数据来测试。
多组数据
我们可以用一个数组来加入多组测试数据
package common
import "testing"
func TestAdd(t *testing.T) {
params := [4]struct{
A int
B int
Expect int
}{
{A:1,B: 5,Expect: 6},
{A:0,B: 5,Expect: 5},
{A:-2,B: 0,Expect: -2},
{A:10,B: -10,Expect: 0},
}
for _,v := range params{
reality := Add(v.A, v.B)
if reality != v.Expect {
t.Errorf("DestroyList error: expect %v,but got %v", v.Expect, reality)
}
}
}
基准测试
用来测试某些操作的性能。
简单示例
还是对上次的Add()函数进行基准测试:
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(19,17)
}
}
运行测试命令
go test -bench=BenchmarkAdd (-bench :选项运行基准测试,实际上它还有一个功能就是和 -run 一样可以跟一个正则表达式,另外还可以在后面加选项:-benchmem来显示内存分配统计数据: go test -bench=BenchmarkAdd -benchmem)
测试函数后面的16表示GOMAXPROCS。一共运行1000000000次,平均耗时0.215 ns/op,消耗内存0 B/op,内存分配次数0 allocs/op。待测函数没有分配额为的内存,我们再看一个例子:
用基准测试引导优化
待测函数
package common
// 获取map[string]int的值
func GetMapValue(a map[string]int) []int {
var b []int
for _,v := range a {
b = append(b,v)
}
return b
}
测试函数
package common
import "testing"
func BenchmarkGetMapValue(b *testing.B) {
a := map[string]int{"a":1,"b":2,"c":3,"d":4,"e":5,"f":6,"g":7,"h":8,"i":9,"j":0}
// 重置了测试时间
b.ResetTimer()
for i := 0; i < b.N; i++ {
GetMapValue(a)
}
}
测试结果
总共测试5163984次,平均每次耗时243 ns/op,消耗内存248 B/op,分配内存次数:5 allocs/op
更改待测函数
package common
func GetMapValue(a map[string]int) []int {
b := make([]int,0,len(a))
for _,v := range a {
b = append(b,v)
}
return b
}
再次测试
这次程序运行时间减少了近一半,内存减少了近70%, 分配次数减少了80%。
API接口的测试
我们以gin框架为例子,首先来看一下gin官方的测试示例:
接口代码
package main
import "github.com/gin-gonic/gin"
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
return r
}
func main() {
r := setupRouter()
r.Run(":8080")
}
测试代码
package main
import (
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
func TestPingRoute(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Equal(t, "pong", w.Body.String())
}
测试结果
调整后的版本
我们调整一下,使它更像我们平时的代码。
Get请求
接口代码
package main
import "github.com/gin-gonic/gin"
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
type SetupRouterRequest struct {
Page int64 `form:"page" binding:"required"`
PageSize int64 `form:"page_size" binding:"required"`
}
type SetupRouterResponse struct {
Page int64 `json:"page"`
PageSize int64 `json:"page_size"`
}
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
var request SetupRouterRequest
if err := c.ShouldBindQuery(&request); err != nil {
c.JSON(200, Response{
Msg: err.Error(),
Code: 400,
Data: nil,
})
return
}
c.JSON(200, Response{
Msg: "SUCCESS",
Code: 200,
Data: SetupRouterResponse{
Page: request.Page,
PageSize: request.PageSize,
},
})
})
return r
}
func main() {
r := setupRouter()
r.Run(":8080")
}
测试代码
package main
import (
"encoding/json"
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestPingRoute(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
url := "/ping"
params := "?page=1&page_size=10"
req, _ := http.NewRequest("GET", fmt.Sprintf("%s%s",url,params), nil)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
body,_ := ioutil.ReadAll(w.Body)
var response Response
if err := json.Unmarshal(body,&response); err != nil {
t.Error(err)
}
assert.Equal(t, 200,response.Code)
var routeRes SetupRouterResponse
if err := JsonMarsh(response.Data,&routeRes); err != nil {
t.Error(err)
}
assert.Equal(t, SetupRouterResponse{
Page: int64(1),
PageSize: int64(10),
},routeRes)
}
// input传入参数,需要转换的值
// output : &b, 指针类型,转出的结果
func JsonMarsh(input interface{}, output interface{}) error {
byteData, err := json.Marshal(input)
if err != nil {
return err
}
err = json.Unmarshal(byteData, output)
if err != nil {
return err
}
return nil
}
测试结果
Post请求
接口代码
package main
import "github.com/gin-gonic/gin"
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
type SetupRouterRequest struct {
Page int64 `json:"page" binding:"required"`
PageSize int64 `json:"page_size" binding:"required"`
}
type SetupRouterResponse struct {
Page int64 `json:"page"`
PageSize int64 `json:"page_size"`
}
func setupRouter() *gin.Engine {
r := gin.Default()
r.POST("/ping", func(c *gin.Context) {
var request SetupRouterRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(200, Response{
Msg: err.Error(),
Code: 400,
Data: nil,
})
return
}
c.JSON(200, Response{
Msg: "SUCCESS",
Code: 200,
Data: SetupRouterResponse{
Page: request.Page,
PageSize: request.PageSize,
},
})
})
return r
}
func main() {
r := setupRouter()
r.Run(":8080")
}
测试代码
package main
import (
"bytes"
"encoding/json"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestPingRoute(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
url := "/ping"
param := SetupRouterRequest{
Page: 1,
PageSize: 10,
}
paramByte, err := json.Marshal(param)
if err != nil {
t.Error(t)
}
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(paramByte))
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
body, _ := ioutil.ReadAll(w.Body)
var response Response
if err := json.Unmarshal(body, &response); err != nil {
t.Error(err)
}
assert.Equal(t, 200, response.Code)
var routeRes SetupRouterResponse
if err := JsonMarsh(response.Data, &routeRes); err != nil {
t.Error(err)
}
assert.Equal(t, SetupRouterResponse{
Page: int64(1),
PageSize: int64(10),
}, routeRes)
}
// input传入参数,需要转换的值
// output : &b, 指针类型,转出的结果
func JsonMarsh(input interface{}, output interface{}) error {
byteData, err := json.Marshal(input)
if err != nil {
return err
}
err = json.Unmarshal(byteData, output)
if err != nil {
return err
}
return nil
}
type TestPingRouteType struct {
request SetupRouterRequest
response SetupRouterResponse
}
func TestPingRouteV2(t *testing.T) {
var (
router = setupRouter()
w = httptest.NewRecorder()
url = "/ping"
)
data := []TestPingRouteType{
{
request: SetupRouterRequest{
Page: 1,
PageSize: 10,
},
response: SetupRouterResponse{
Page: 1,
PageSize: 10,
},
},
}
for _,v := range data {
paramByte, err := json.Marshal(v.request)
if err != nil {
t.Error(t)
}
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(paramByte))
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
body, _ := ioutil.ReadAll(w.Body)
var response Response
if err := json.Unmarshal(body, &response); err != nil {
t.Error(err)
}
assert.Equal(t, 200, response.Code)
var routeRes SetupRouterResponse
if err := JsonMarsh(response.Data, &routeRes); err != nil {
t.Error(err)
}
assert.Equal(t, v.response, routeRes)
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战