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)
    }
}

测试结果

posted @ 2022-02-13 12:45  EthanWell  阅读(421)  评论(0编辑  收藏  举报