zoukankan      html  css  js  c++  java
  • 第八章 go单元测试--表格驱动测试, 性能测试

    一. go语言单元测试写法

    1. 文件命令, 测试内容+ _test

    2. 测试的方法名以Test开头. 参数为(test *Test)

    3. 测试文件和源文件放在同一个目录中

    例:

    package TestingDebug
    
    func Add(a, b int) int {
        return b + a
    }
    package TestingDebug
    
    import (
        "fmt"
        "testing"
    )
    
    func TestAdd(t *testing.T) {
        var test = []struct{
            a, b, c int
        }{
            {1, 2, 3},
            {3, 5, 8},
            {2, 4, 5},
        }
    
        for _, tt := range test  {
            if  actural := Add(tt.a, tt.b) ; actural != tt.c {
                fmt.Printf("%d + %d, except:%d, actual:%d", tt.a, tt.b, tt.c, actural)
            }
        }
    }

    需要注意几点

    1. 同目录下文件相互调用, 不需要加包名

    2. 测试文件与源文件在同一个目录下,  否则技术不出来测试代码覆盖率.

    二. 如何测试代码覆盖率

     然后点击TestingDebug进入文件夹, 点击测试的文件. 看右侧绿线, 表示测试覆盖的代码. 这里覆盖了100%

     三. 性能测试BenchMark

    性能测试使用Testing.B

    比如, 我们想要测试一个复杂一些的数据的累加, 看看性能如何. 

    /**
     * 性能测试
     */
    func BenchmarkAdd(bb *testing.B) {
        var a, b, c int
        a = 123
        b = 4557
        c = 4680
        for i := 0; i<bb.N ; i++  {
            if  actural := Add(a, b) ; actural != c {
                fmt.Printf("%d + %d, except:%d, actual:%d", a, b, c, actural)
            }
        }
    }

    bb.N表示的是系统自动计算的一个循环次数, 我们不用自己指定.

    goos: darwin
    goarch: amd64
    pkg: aaa/TestingDebug
    BenchmarkAdd-8       1000000000             0.317 ns/op
    PASS

    以上是测试结果. 1000000000 代表测试的次数是10亿次. 0.317 ns/op每个操作执行的时间是0.317ns

     四. 性能调优--使用pprof进行性能优化

    如上一步, 我们对代码进行了性能测试. 每个操作执行时间是0.317ns. 那么, 假如有一个稍微复杂一些的例子, 我如何看出花费了这么多时间, 都是在哪个步骤花的? 哪个步骤耗费时间最多呢?

    我们使用命令来看

    go test -bench . -cpuprofile cpu.out

    执行上面的命令生成一个cpu.out的文件, 查看cpu运行的情况

    我们使用less查看文件: less cpu.out

     cpu.out是一个二进制文件,我们没办法打开. 我们可以使用命令打开

    go tool pprof cpu.out

     这里我们可以通过pprof的web视图来查看到底那个步骤耗时最多. 但是看红色字, 查看web视图,需要安装一个软件Graphviz

    安装Graphviz, 下载地址: http://www.graphviz.org/. 我是mac, 下载一个mac版本的: 

    下载命令: brew install graphviz

    安装成功以后, 数据命令 go tool pprof cpu.out , 然后输入web , 会生产一个svg文件, 用浏览器打开我们就会看到效果了

    这就是对简单程序的一个分析. 他在每个阶段用时是多少. 因为我们这里是很简单的一个demo, 所以耗时很少了.  

    五. 测试http服务器

    http测试分为两种

    1. 通过使用假的Request/Response实现 : 运行速度更快, 可以测试更多种情况, 更像单元测试. 

    2. 通过启服务器: 模拟的诊室服务, 覆盖代码更多.

    1. 使用假的Request和Response模拟http请求实现单元测试 

    分析:

    首先: 明确我们的目标, 要对谁做测试. 我们要对封装的错误处理进行测试. 

    第二: 测试有输入和输出. 预计输入输出, 真实输入输出. 我们这里输入是谁呢?通过包装类WrapError分析, 入参是对http请求业务逻辑的处理, 出参是包装的错误处理, 包括错误码和错误消息

    所以, 单元测试的表格的结构体怎么写呢?

    test := []struct{
        h appHandler     // 入参
        Code int        // 出参
        Message string    // 出参
    } {
        
    }

    入参是一个appHandler函数. 因此我们定义一个函数,来模拟返回的错误信息. 异常的种类有很多,先定义一个panic

    func handlerPanic(writer http.ResponseWriter, request *http.Request) error {
        panic("panic error")
    }

    接下来模拟http请求, 判断异常处理是否正确, 完整代码如下:

    package main
    
    import (
        "io/ioutil"
        "net/http"
        "net/http/httptest"
        "strings"
        "testing"
    )
    
    
    func handlerPanic(writer http.ResponseWriter, request *http.Request) error {
        panic("panic error")
    }
    
    func TestWrapError(t *testing.T) {
        // 我们测试的异常的封装方法 那么对于单元测试来说, 无非就是入参和出参
        test := []struct{
            h appHandler     // 入参
            Code int        // 出参
            Message string    // 出参
        } {
            {handlerPanic, 500, "Internal Server Error"},
        }
    
        for _, tt := range test {
            // 要测试的方法WrapError
            f := WrapError(tt.h)
            // 模拟Request和response
            response := httptest.NewRecorder()
            request := httptest.NewRequest(http.MethodGet, "http://www.baidu.com", nil)
            f(response, request)
            //读取reponse返回的body
            b, _ := ioutil.ReadAll(response.Body)
            body := strings.Trim(string(b), "
    ")
            // 测试返回值和预期是否一致
            if tt.Code != response.Code || tt.Message != body {
                t.Errorf("预期返回--code:%d, message:%s 
     实际返回--code:%d, message:%s", tt.Code,
                    tt.Message, response.Code, body)
            }
        }
    }

    测试用户自定义异常, 我们在测试用户自定义信息

    package main
    
    import (
        "io/ioutil"
        "net/http"
        "net/http/httptest"
        "strings"
        "testing"
    )
    //用户自定义异常
    type testUserError string
    
    func (u testUserError) Error() string{
        return u.Message()
    }
    
    func (u testUserError) Message() string {
        return string(u)
    }
    
    func handlerUserError(writer http.ResponseWriter, request *http.Request) error {
        return testUserError("user error")
    }
    
    
    func handlerPanic(writer http.ResponseWriter, request *http.Request) error {
        panic("panic error")
    }
    
    func TestWrapError(t *testing.T) {
        // 我们测试的异常的封装方法 那么对于单元测试来说, 无非就是入参和出参
        test := []struct{
            h appHandler     // 入参
            Code int        // 出参
            Message string    // 出参
        } {
            {handlerPanic, 500, "Internal Server Error"},
            {handlerUserError, 400, "user error"},
        }
    
        for _, tt := range test {
            // 要测试的方法WrapError
            f := WrapError(tt.h)
            // 模拟Request和response
            response := httptest.NewRecorder()
            request := httptest.NewRequest(http.MethodGet, "http://www.baidu.com", nil)
            f(response, request)
            //读取reponse返回的body
            b, _ := ioutil.ReadAll(response.Body)
            body := strings.Trim(string(b), "
    ")
            // 测试返回值和预期是否一致
            if tt.Code != response.Code || tt.Message != body {
                t.Errorf("预期返回--code:%d, message:%s 
     实际返回--code:%d, message:%s", tt.Code,
                    tt.Message, response.Code, body)
            }
        }
    }

    接下来查看代码覆盖率 35%. 接下来补全测试代码, 提高代码覆盖率

    package main
    
    import (
        "github.com/pkg/errors"
        "io/ioutil"
        "net/http"
        "net/http/httptest"
        "os"
        "strings"
        "testing"
    )
    
    type testUserError string
    
    func (u testUserError) Error() string{
        return u.Message()
    }
    
    func (u testUserError) Message() string {
        return string(u)
    }
    
    func handlerUserError(writer http.ResponseWriter, request *http.Request) error {
        return testUserError("user error")
    }
    
    
    func handlerPanicError(writer http.ResponseWriter, request *http.Request) error {
        panic("panic error")
    }
    
    func handlerNotFountError(writer http.ResponseWriter, request *http.Request) error {
        return os.ErrNotExist
    }
    
    func handlerForbiddenError(writer http.ResponseWriter, request *http.Request) error {
        return os.ErrPermission
    }
    
    func otherError(writer http.ResponseWriter, request *http.Request) error {
        return errors.New("123")
    }
    
    func noError(writer http.ResponseWriter, request *http.Request) error {
        return nil
    }
    
    func TestWrapError(t *testing.T) {
        // 我们测试的异常的封装方法 那么对于单元测试来说, 无非就是入参和出参
        test := []struct{
            h appHandler     // 入参
            Code int        // 出参
            Message string    // 出参
        } {
            {handlerPanicError, 500, "Internal Server Error"},
            {handlerUserError, 400, "user error"},
            {handlerNotFountError, 404, "Not Found"},
            {handlerForbiddenError, 403, "Forbidden"},
            {otherError, 500, "Internal Server Error"},
            {noError, 200, ""},
        }
    
        for _, tt := range test {
            // 要测试的方法WrapError
            f := WrapError(tt.h)
            // 模拟Request和response
            response := httptest.NewRecorder()
            request := httptest.NewRequest(http.MethodGet, "http://www.baidu.com", nil)
            f(response, request)
            //读取reponse返回的body
            b, _ := ioutil.ReadAll(response.Body)
            body := strings.Trim(string(b), "
    ")
            // 测试返回值和预期是否一致
            if tt.Code != response.Code || tt.Message != body {
                t.Errorf("预期返回--code:%d, message:%s 
     实际返回--code:%d, message:%s", tt.Code,
                    tt.Message, response.Code, body)
            }
        }
    }

    再次查看代码覆盖率

     达到了80%, 只有最后的main方法没有被覆盖. ok了

    2. 使用真实的http请求进行测试

    // 真实http请求
    func TestWrapErrorForServer(t *testing.T) {
        for _, tt := range test  {
            // 要测试的方法WrapError
            f := WrapError(tt.h)
            
            s := httptest.NewServer(http.HandlerFunc(f))
            // 这里url不用填自己的, httptest在new server的时候帮我们定义好了一个url
            response, _ := s.Client().Get(s.URL)
    
            b, _ := ioutil.ReadAll(response.Body)
            body := strings.Trim(string(b), "
    ")
    
            if tt.Code != response.StatusCode || tt.Message != body {
                t.Errorf("except--code: %d, message: %s 
     actual--code:%d, message:%s",
                    tt.Code, tt.Message, response.StatusCode, body)
            }
        }
    }

    模拟数据的部分, 两种类型的http请求是一样的, 被提取到外面了, 最终完整代码如下:

    package main
    
    import (
        "github.com/pkg/errors"
        "io/ioutil"
        "net/http"
        "net/http/httptest"
        "os"
        "strings"
        "testing"
    )
    
    type testUserError string
    
    func (u testUserError) Error() string{
        return u.Message()
    }
    
    func (u testUserError) Message() string {
        return string(u)
    }
    
    func handlerUserError(writer http.ResponseWriter, request *http.Request) error {
        return testUserError("user error")
    }
    
    
    func handlerPanicError(writer http.ResponseWriter, request *http.Request) error {
        panic("panic error")
    }
    
    func handlerNotFountError(writer http.ResponseWriter, request *http.Request) error {
        return os.ErrNotExist
    }
    
    func handlerForbiddenError(writer http.ResponseWriter, request *http.Request) error {
        return os.ErrPermission
    }
    
    func otherError(writer http.ResponseWriter, request *http.Request) error {
        return errors.New("123")
    }
    
    func noError(writer http.ResponseWriter, request *http.Request) error {
        return nil
    }
    
    // 测试数据
    var test = []struct{
        h appHandler     // 入参
        Code int        // 出参
        Message string    // 出参
    } {
        {handlerPanicError, 500, "Internal Server Error"},
        {handlerUserError, 400, "user error"},
        {handlerNotFountError, 404, "Not Found"},
        {handlerForbiddenError, 403, "Forbidden"},
        {otherError, 500, "Internal Server Error"},
        {noError, 200, ""},
    }
    
    // 模拟http请求
    func TestWrapError(t *testing.T) {
        for _, tt := range test {
            // 要测试的方法WrapError
            f := WrapError(tt.h)
            // 模拟Request和response
            response := httptest.NewRecorder()
            request := httptest.NewRequest(http.MethodGet, "http://www.baidu.com", nil)
            f(response, request)
            //读取reponse返回的body
            verify(response.Result(), tt, t)
        }
    }
    
    // 真实http请求
    func TestWrapErrorForServer(t *testing.T) {
        for _, tt := range test  {
            // 要测试的方法WrapError
            f := WrapError(tt.h)
    
            s := httptest.NewServer(http.HandlerFunc(f))
            // 这里url不用填自己的, httptest在new server的时候帮我们定义好了一个url
            response, _ := s.Client().Get(s.URL)
    
            verify(response, tt, t)
        }
    }
    
    func verify(response *http.Response, tt struct {h appHandler;Code int;Message string}, t *testing.T) {
        b, _ := ioutil.ReadAll(response.Body)
        body := strings.Trim(string(b), "
    ")
        if tt.Code != response.StatusCode || tt.Message != body {
            t.Errorf("except--code: %d, message: %s 
     actual--code:%d, message:%s",
                tt.Code, tt.Message, response.StatusCode, body)
        }
    
    }
    package fileListener
    
    import (
        "io/ioutil"
        "net/http"
        "os"
        "strings"
    )
    
    type UserError string
    
    func (u UserError) Error() string{
        return u.Message()
    }
    
    func (u UserError) Message() string {
        return string(u)
    }
    
    func FileHandler(writer http.ResponseWriter, request *http.Request) error {
        // 获取url路径, 路径是/list/之后的部分
        if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
    
    
            return UserError("url 不是已list开头")
        }
        path := request.URL.Path[len("/list/"):]
        // 打开文件
        file, err := os.Open(path)
        if err != nil {
            return err
        }
        defer file.Close()
    
        // 读出文件
        b, err := ioutil.ReadAll(file)
        if err != nil {
            return err
        }
    
        // 写入文件到页面
        writer.Write(b)
        return nil
    }
    package main
    
    import (
        "aaa/errorhandling/filelistenerserver/fileListener"
        "github.com/kelseyhightower/confd/log"
        "net/http"
        "os"
    )
    
    // 定义一个函数类型的结构, 返回值是erro
    type appHandler func(writer http.ResponseWriter, request *http.Request) error
    
    // 封装error
    func WrapError(handler appHandler) func (http.ResponseWriter, *http.Request) {
        return func(writer http.ResponseWriter, request *http.Request) {
            defer func(){
                if r := recover(); r != nil {
                    log.Info("发生异常")
                    http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
                }
            }()
    
            // 执行原来的逻辑. 然后增加error的错误处理
            err := handler(writer, request)
            if err != nil {
                if userErr, ok := err.(userError); ok {
                    http.Error(writer, userErr.Message(), http.StatusBadRequest)
                    return
                }
                code := http.StatusOK
                switch {
                case os.IsNotExist(err):
                    code = http.StatusNotFound
    
                case os.IsPermission(err):
                    code = http.StatusForbidden
                default:
                    code = http.StatusInternalServerError
                }
                http.Error(writer, http.StatusText(code), code)
            }
        }
    }
    
    type userError interface {
        error        // 系统异常
        Message() string    // 用户自定义异常
    }
    // 我们来模拟一个web服务器. 在url上输入一个地址, 然后显示文件内容
    // 做一个显示文件的web server
    func main() {
        http.HandleFunc("/", WrapError(fileListener.FileHandler))
    
        // 监听端口:8888
        err := http.ListenAndServe(":8888", nil)
        if  err != nil {
            panic("err")
        }
    }

    代码结构

  • 相关阅读:
    docker 部署 禅道系统
    docker 部署 jenkins
    运筹方法
    软件工程基础知识
    操作系统知识
    程序设计语言基础知识
    计算机组成与配置
    oracle触发器
    性能测试监控工具的使用
    数据库设计范式
  • 原文地址:https://www.cnblogs.com/ITPower/p/12315637.html
Copyright © 2011-2022 走看看