• Golang的Test的用法


    正常来说,写了go代码跑起来,是将整个工程跑起来,比如一个goframe项目那么直接将g.server跑起来,也就是从入口文件

    然后通过postman访问路由验证

    但是很多时候有一些简单的静态方法像java可以在spring的项目之中任意文件起一个main函数调用验证static的一些方法

    那么go的话,难道测试单一模块需要到处写main函数吗?还是非得将整个工程跑起来吗?

    试用一下golang test

    主要功能点: 

    1、不用到处写main函数调用就可以测试单一方法;

    2、可以使用断言方法方便多次验证接口通过率;

    3、看起来高大上,如果在项目之中到处写用于test的main函数不是很low么,一不小心提交到git上了;

    4、写好测试逻辑后可以很方便的多次、高频测试;

    5、方便压测统计

    使用方式:

    1 编写测试代码*_test.go , 注意:文件名必须是 _test.go结尾

    package c
    
    import (
        "testing"
    )
    
    // 验证gconv struct会不会抛出空指针异常
    func Test_Trim(t *testing.T) {
        t.Log("start test", t.Name())
        c := Add(1, 2)
        if c != 3 {
            // 判定不会爆空指针异常
            t.Log("Add function error")
            t.FailNow()
        }
        t.Log("very good")
    }
    
    func Add(a int, b int) int {
        return a + b + 1
    }

    2 测试单个文件,一定要带上被测试的原文件(所有有依赖的文件名都要带上,所以这个一般不会使用,不会测试单个文件,只限制单个方法就好了)

    go test -v wechat_test.go wechat.go

    3 测试单个方法

    go test -v wechat_test.go -test.run TestRefreshAccessToken //如果不指定文件名,会将当前目录的所有test方法进行测试 、 如果不指定方法名将会验证所有方法

    最常用的用法当然是

    go test -test.run TestGetProductName

    测试单个方法,这样也不用指定有依赖的文件列表

    4 压力测试

    package c
    
    import (
        "testing"
    
        "github.com/stretchr/testify/assert"
    )
    
    // 验证gconv struct会不会抛出空指针异常
    func TestGetProductName(t *testing.T) {
        productID := 3
        t.Log("start TestGetUserName", productID)
        c := GetProductName(1)
        assert.Equal(t, "iphone 12", c, "商品名称必须是 iphone 12")
    }
    
    // GetProductName 获取商品名称
    func GetProductName(productID int) string {
        // 从redis获取商品数据
        // 如果redis没有就查询数据库
        return "iphone 12"
    }
    
    func TestGetUserName(t *testing.T) {
        userID := 2
        t.Log("start TestGetUserName", userID)
        c := GetUserName(userID)
        assert.Equal(t, "jack", c, "商品名称必须是 jack")
    }
    
    // GetUserName 获取用户名称
    func GetUserName(userID int) string {
        // 从redis获取用户名称
        // 如果redis没有就查询数据库
        return "jack"
    }
    
    // 1 基准测试的代码文件必须以_test.go结尾
    // 2 基准测试的函数必须以Benchmark开头,必须是可导出的
    // 3 基准测试函数必须接受一个指向Benchmark类型的指针作为唯一参数
    // 4 基准测试函数不能有返回值
    // 5 b.ResetTimer是重置计时器,这样可以避免for循环之前的初始化代码的干扰
    // 6 最后的for循环很重要,被测试的代码要放到循环里
    // 7 b.N是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能
    
    // BenchmarkGetProductName 压力测试获取商品名称接口
    func BenchmarkGetProductName(b *testing.B) {
        num := 10
        // 之所以增加这个函数是之前的初始化时间不算在压测的这个接口的执行时间内
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            GetUserName(num)
            // if i == 12 {
            //     b.Error()
            // }
        }
    }
    
    // BenchmarkGetUserName 压力测试获取商品名称接口
    func BenchmarkGetUserName(b *testing.B) {
        num := 10
        // 之所以增加这个函数是之前的初始化时间不算在压测的这个接口的执行时间内
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            GetUserName(num)
        }
    }

    单元测试获取商品名称接口:

    // 指定仅仅运行 product_test 文件的 TestGetProductName 函数
    
    go test -v product_test.go -test.run TestGetProductName

     如何对获取商品名称接口进行压测呢?

    推荐阅读:https://cloud.tencent.com/developer/article/1469185

    验证代码:

    // 1 基准测试的代码文件必须以_test.go结尾
    // 2 基准测试的函数必须以Benchmark开头,必须是可导出的
    // 3 基准测试函数必须接受一个指向Benchmark类型的指针作为唯一参数
    // 4 基准测试函数不能有返回值
    // 5 b.ResetTimer是重置计时器,这样可以避免for循环之前的初始化代码的干扰
    // 6 最后的for循环很重要,被测试的代码要放到循环里
    // 7 b.N是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能
    
    // BenchmarkGetProductName 压力测试获取商品名称接口
    func BenchmarkGetProductName(b *testing.B) {
    b.N = 1000000 // 修改测试次数是100w次 num :
    = 10 // 之所以增加这个函数是之前的初始化时间不算在压测的这个接口的执行时间内 b.ResetTimer() for i := 0; i < b.N; i++ { GetProductName(num) } } // BenchmarkGetUserName 压力测试获取商品名称接口 func BenchmarkGetUserName(b *testing.B) { num := 10 // 之所以增加这个函数是之前的初始化时间不算在压测的这个接口的执行时间内 b.ResetTimer() for i := 0; i < b.N; i++ { GetUserName(num) } }

    现在对获取商品接口压测 2000次并发调用:

    go test -bench=. -run=none

    - bench 表示对哪些函数左压测 ,如果只对 BenchmarkGetUserName 压测则 -bench=BenchmarkGetUserName

    输出:

    $ go test -bench=. -run=none
    goos: windows
    goarch: amd64
    pkg: example.com/m/v
    cpu: Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz
    BenchmarkGetProductName-4       1000000000               0.2947 ns/op
    BenchmarkGetUserName-4          1000000000               0.2953 ns/op
    PASS
    ok      example.com/m/v 3.688s

    函数执行次数  1000000000, 各自平均执行时间是 0.2947 纳秒和 0.2953纳秒

    总共执行时长是 3.688s

    还可以使用 pprof 查看内存和CPU占用

    现在我们可以知道我们的接口执行完成需要花费的时间,但是我们并不知道调用接口的时候有没有出现请求失败的情况,

    比如在一万个请求过来的时候,它的执行,性能瓶颈是在MySQL还是Redis,会不会变得等待很久,就是一个临界点,10qps的时候每个接口执行1s,那1w个qps的话执行时长是多少?

    如何验证?

    感觉基准测试根本不满足,宁愿自己手写脚本验证,并且请求过去

    目前更多的就是简单的拿到一段代码执行的CPU、内存消耗、执行时长,但是并不能用来做并发的压测

    一个连接数据库的小脚本、可以用于测试:

    package test
    
    import (
        "database/sql"
    
        _ "github.com/go-sql-driver/mysql"
    )
    
    var (
        dbhostsip  = "127.0.0.1:3306"
        dbusername = "user"
        dbpassowrd = "123"
        dbname     = "test"
    )
    
    type mysql_db struct {
        db *sql.DB //定义结构体
    }
    
    func (f *mysql_db) mysql_open() { //打开
        Odb, err := sql.Open("mysql", dbusername+":"+dbpassowrd+"@tcp("+dbhostsip+")/"+dbname)
        if err != nil {
            panic(err)
        }
        f.db = Odb
    }
    
    func (f *mysql_db) mysql_close() { //关闭
        defer f.db.Close()
    }
    
    func (f *mysql_db) mysql_select(sql_data string) string {
        rows, err := f.db.Query(sql_data)
        if err != nil {
            println(err)
        }
        user_id := ""
        for rows.Next() {
            var id int
            var name string
            err = rows.Scan(&id, &name)
            if err != nil {
                panic(err)
            }
            user_id = name
        }
        return user_id
    }
    
    // type Log struct {
    //     ID   string `json:"id"`
    //     Name string `json:"id"`
    // }
    
    func GetDB() string {
        db := &mysql_db{}
        db.mysql_open()
        u := db.mysql_select("SELECT * FROM user")
        db.mysql_close() //关闭
        return u
    }

     优秀的写法示例:

    go-zero框架的单元测试示例

    参考文章:

    https://goframe.org/pages/viewpage.action?pageId=1114153

    https://blog.csdn.net/nimei31/article/details/81385207

    http://c.biancheng.net/view/124.html

    单元测试工具包:

    https://github.com/stretchr/testify

  • 相关阅读:
    进程与线程
    HDOJ搜索专题之Catch That Cow
    HDOJ搜索专题之Red and Black
    COJ1026(过河卒)
    HDOJ搜索专题之Prime Ring Problem
    COJ1113(Emperor And His Knight)
    HDOJ搜索专题之胜利大逃亡
    HDOJ搜索专题之翻纸牌游戏
    HDOJ搜索专题之Counting Sheep
    HDOJ搜索专题之Robot Motion
  • 原文地址:https://www.cnblogs.com/xuweiqiang/p/16181384.html
Copyright © 2020-2023  润新知