51Testing软件测试论坛

标题: gin单元测试真香实践 [打印本页]

作者: lsekfe    时间: 2020-8-27 10:19
标题: gin单元测试真香实践
1、单元测试常识
-单元测试文件名必须为xxx_test.go(其中xxx为业务逻辑程序)
  -分为基础测试、基准测试、案例测试
  -基础测试的函数名必须为Testxxx(xxx可用来识别业务逻辑函数)
  -基准测试必须以BenchmarkXXX函数名出现
  -案例测试必须要已ExampleXXX函数名出线
  -单元测试函数参数必须为t *testing.T(测试框架强要求)
  -测试程序和被测试程序文件在一个包package中(可以不放,我自己会单独放一个tests目录)
  -测试用例文件不会参与正常源码编译,不会被包含到可执行文件中
  -必须import testing这个包

  常见命令有很多人说了,我就提供个文档吧
  2、为什么要写单元测试
  保证代码的质量,保证每个函数是可运行,运行结果是正确的,保证写出来的代码性能是好的、同时使用单元测试很直观的可以提高开发效率,你不需要每次编译你的代码, 然后在去postman模拟参数测试、单元可以可以模拟http请求,并且可以帮助你进行性能测试以及代码覆盖率,导出测试报告等等
  3、在gin框架中单元测试该如何写
  TestMain
  在写测试时,有时需要在测试之前或之后进行额外的设置(setup)或拆卸(teardown);有时,测试还需要控制在主线程上运行的代码。为了支持这些需求,testing 包提供了 TestMain 函数 :
  1. package tests

  2. import (

  3.    "testing"
  4.     "fmt"
  5.     "os"
  6.    
  7.     "github.com/gin-gonic/gin"
  8.    
  9.     "go-api/config"
  10. )
  11. func setup() {
  12.     gin.SetMode(gin.TestMode)
  13.     fmt.Println(config.AppSetting.JwtSecret);
  14.     fmt.Println("Before all tests")
  15. }
  16. func teardown() {
  17.     fmt.Println("After all tests")
  18. }
  19. func TestMain(m *testing.M)  {
  20.     setup()
  21.     fmt.Println("Test begins....")
  22.     code := m.Run() // 如果不加这句,只会执行Main
  23.     teardown()
  24.     os.Exit(code)
  25. }
复制代码
gin 单元测试案例
  1.  package main

  2. funcsetupRouter() *gin.Engine{
  3.     r := gin.Default()
  4.     r.GET("/ping", func(c *gin.Context) {
  5.         c.String(200,"pong")
  6.     })
  7.     return r
  8. }

  9. func main() {
  10.     r := setupRouter()
  11.     r.Run(":8080")
  12. }

  13. package main

  14. import (
  15.    "net/http"
  16.     "net/http/httptest"
  17.     "testing"

  18.     "github.com/stretchr/testify/assert"
  19. )

  20. funcTestPingRoute(t *testing.T) {
  21.     router := setupRouter()

  22.     w := httptest.NewRecorder()
  23.     req, _ := http.NewRequest("GET","/ping", nil)
  24.     router.ServeHTTP(w, req)

  25.     assert.Equal(t, 200, w.Code)
  26.     assert.Equal(t,"pong", w.Body.String())
  27. }
复制代码
4、gin该如何优雅的写单元测试
  使用Table-Driven Test
  1. func TestFib(t *testing.T) {
  2.     var fibTests = []struct {
  3.         in       int // input
  4.         expected int // expected result
  5.     }{
  6.         {1, 1},
  7.         {2, 1},
  8.         {3, 2},
  9.         {4, 3},
  10.         {5, 5},
  11.         {6, 8},
  12.         {7, 13},
  13.     }

  14.     for _, tt := range fibTests {
  15.         actual := Fib(tt.in)
  16.         if actual != tt.expected {
  17.             t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected)
  18.         }
  19.     }
  20. }
复制代码
 封装一些test helper
  1. package tests

  2. import (
  3.     "io"
  4.     "net/http"
  5.     "net/http/httptest"

  6.     "bytes"
  7.     "fmt"
  8.     "testing"

  9.     "github.com/gin-gonic/gin"
  10.     "github.com/stretchr/testify/assert"

  11.     "go-api/routes"
  12.     "go-api/tool"
  13. )

  14. type TestCase struct {
  15.     code         int         //状态码
  16.     param        string      //参数
  17.     method       string      //请求类型
  18.     desc         string      //描述
  19.     showBody     bool        //是否展示返回
  20.     errMsg       string      //错误信息
  21.     url          string      //链接
  22.     content_type string      //
  23.     ext1         interface{} //自定义1
  24.     ext2         interface{} //自定义2
  25. }

  26. func NewBufferString(body string) io.Reader {
  27.     return bytes.NewBufferString(body)
  28. }

  29. func PerformRequest(mothod, url, content_type string, body string) (c *gin.Context, r *http.Request, w *httptest.ResponseRecorder) {
  30.     router := routes.InitRouter()
  31.     w = httptest.NewRecorder()
  32.     c, _ = gin.CreateTestContext(w)
  33.     r = httptest.NewRequest(mothod, url, NewBufferString(body))
  34.     c.Request = r
  35.     c.Request.Header.Set("Content-Type", content_type)
  36.     router.ServeHTTP(w, r)
  37.     return
  38. }


  39. func call(t *testing.T,testcase []TestCase){
  40.     for k, v := range testcase {
  41.         _, _, w := PerformRequest(v.method, v.url, v.content_type, v.param)
  42.         //assert.Contains(t, w.Body.String(),fmt.Sprintf("\"error_code\":%d",v.code))
  43.         fmt.Println()
  44.         fmt.Printf("第%d个测试用例:%s", k+1, v.desc)
  45.         if v.showBody {
  46.             fmt.Printf("接口返回%s", w.Body.String())
  47.             fmt.Println()
  48.         }

  49.         s := struct {
  50.             Error_code int         `json:"error_code"`
  51.             Msg        string      `json:"msg"`
  52.             Data       interface{} `json:"data"`
  53.         }{}
  54.         err := tool.JsonToStruct([]byte(w.Body.String()), &s)
  55.         assert.NoError(t, err)
  56.         assert.Equal(t, v.errMsg, s.Msg, "错误信息不一致")
  57.         assert.Equal(t, v.code, s.Error_code, "错误码不一致")
  58.     }
  59. }
复制代码
 使用子测试(subtests)
  来控制嵌套测试和执行顺序、解决测试套件依赖性
  1. func TestFoo(t *testing.T) {
  2.     // <setup code>
  3.     t.Run("A=1", func(t *testing.T) { ... })
  4.     t.Run("A=2", func(t *testing.T) { ... })
  5.     t.Run("B=1", func(t *testing.T) { ... })
  6.     // <tear-down code>
  7. }
复制代码












欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2