golang 测试、性能分析 | 青训营

45 阅读2分钟

测试

测试Model层

  • user_test.go

    • 测试代码所在文件的名称以_test结尾
    • 对于生产编译,不会包含以_test结尾的文件
    • 对于测试编译,会包含以_test结尾的文件
  • func TestUpdatesModifiedTime(t *testing.T) {...}

    • 测试函数名应以Test开头(需要导出)
    • 函数名需要表达出被验证的特性
    • 测试函数的参数类型是*testing.T,它会提供测试相关的一些工具

    example

    // 被测文件
    package model
    ​
    import "strings"// Company ..
    tyep Company struct {
        ID      int     `json:"id"`
        Name    string  `json:"name"`
        Country string  `json:"country"`
    }
    ​
    // GetCompanyType ..
    func (c *Company) GetCompanyType() (result string) {
        if strings.HasSuffix(c.Name, ".LTD"){
            result = "Limited Liability Company"
        } else {
            result = "Others"
        }
        return
    }
    
    // 测试文件
    package model
    ​
    import "testing"func TestCompanyTypeCorrect(t *testing.T) {
        c := Company {
            ID:         12345,
            Name:       "ABCD .LTD",
            Country:    "China",
        }
        
        companyType := c.GetCompanyType()
        
        if companyType != "Limited Liability Company" {
            t.Errorf("Company's GetCompanyType Method failed to get correct company type")
        }
    }
    

测试Controller

  • 为了尽量保证单元测试的隔离性,测试不要使用例如数据库、外部API、文件系统等外部资源

  • 模拟请求和响应

  • 需要使用net/http/httptest提供的功能

    • NewRequest函数:func NewRequest(method, url string, body io.Reader) (*Request, error)

      • methodHTTP Method
      • url:请求的URL
      • body:请求的Body
      • 返回的*Request可以传递给handler函数
    • ResponseRecorder

      type ResponseRecorder {
          Code int    // 状态码 200、500...
          HeaderMap http.Header   // 响应的header
          Body *bytes.Buffer  // 响应的body
          Flushed bool    //缓存是否被flush了
      }
      
      • 用来捕获从handler返回的响应,只是做记录
      • 可以用于测试断言

      example:

      // 被测文件部分
      func RegisterRoutes() {
          http.HandleFunc("/companies", handleCompany)
      }
      ​
      func handleCompany(w http.ResponseWriter, r *http.Request) {
          c := model.Company {
              ID:         123,
              Name:       "Google",
              Country:    "USA",
          }
          
          enc := json.NewEncoder(w)
          enc.Encode(c)
          
      }
      
      // 测试文件
      ​
      func TestHandleCompanyCorrect(t *testing.T) {
          r := httptest.NewRequest(http.MethodGet, "/companies", nil)
          w := httptest.NewRecoder()
          
          handleCompany(w, r)
          
          result, _ := ioutil.ReadAll(w.Result().Body)
          
          c := model.Company{}
          json.Unmarshal(result, &c)
          
          fi c.ID != 123 {
              t.Errorf("Failed to handle company correctly!")
          }
          
      }
      

性能分析

  • 内存消耗
  • CPU使用
  • 阻塞的goroutine
  • 执行追踪
  • 还有一个Web界面:应用的实时数据

import _ "net/http/pprof"

  • 设置一些监听的URL,它们会提供各类诊断信息

  • go tool pprof http://localhost:8000/debug/pprof/heap // 内存

    • 从应用获取内存dump:应用在使用哪些内存,它们会去哪
  • go tool pprof http://localhost:8000/debug/pprof/profile // CPU

    • CPU的快照,可以看到谁在用CPU
  • go tool pprof http://localhost:8000/debug/pprof/block // goroutine

    • 看到阻塞的goroutine
  • go tool pprof http://localhost:8000/debug/pprof/tarce?seconds=5 // trace

    • 监控这段时间内,什么在执行,什么在调用什么...

部署

go build .
nohup ./it &
或者通过守护进程
sudo vim /etc/systemd/system/go-web.service # (go-web是属于自己起的名)
# go-web.service
[Unit]
Description=GO WEB App running on Ubuntu
​
[Service]
ExecStart=/home/solenovex/it/it # 启动文件的命令
Restart=always  # 设置挂了会自动重启
RestartSec=10   # 重启服务间隔为10秒
killSignal=SIGINT
SyslogIdentifier=go-web-example
User=solenovex  # 执行用户[Install]
WantedBy=multi-user.target