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 函数 :
package tests import ( "testing" "fmt" "os" "github.com/gin-gonic/gin" "go-api/config" ) func setup() { gin.SetMode(gin.TestMode) fmt.Println(config.AppSetting.JwtSecret); fmt.Println("Before all tests") } func teardown() { fmt.Println("After all tests") } func TestMain(m *testing.M) { setup() fmt.Println("Test begins....") code := m.Run() // 如果不加这句,只会执行Main teardown() os.Exit(code) } |
gin 单元测试案例
package main funcsetupRouter() *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 ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) funcTestPingRoute(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()) } |
4、gin该如何优雅的写单元测试
使用Table-Driven Test
func TestFib(t *testing.T) { var fibTests = []struct { in int // input expected int // expected result }{ {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}, {7, 13}, } for _, tt := range fibTests { actual := Fib(tt.in) if actual != tt.expected { t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected) } } } |
封装一些test helper
package tests import ( "io" "net/http" "net/http/httptest" "bytes" "fmt" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "go-api/routes" "go-api/tool" ) type TestCase struct { code int //状态码 param string //参数 method string //请求类型 desc string //描述 showBody bool //是否展示返回 errMsg string //错误信息 url string //链接 content_type string // ext1 interface{} //自定义1 ext2 interface{} //自定义2 } func NewBufferString(body string) io.Reader { return bytes.NewBufferString(body) } func PerformRequest(mothod, url, content_type string, body string) (c *gin.Context, r *http.Request, w *httptest.ResponseRecorder) { router := routes.InitRouter() w = httptest.NewRecorder() c, _ = gin.CreateTestContext(w) r = httptest.NewRequest(mothod, url, NewBufferString(body)) c.Request = r c.Request.Header.Set("Content-Type", content_type) router.ServeHTTP(w, r) return } func call(t *testing.T,testcase []TestCase){ for k, v := range testcase { _, _, w := PerformRequest(v.method, v.url, v.content_type, v.param) //assert.Contains(t, w.Body.String(),fmt.Sprintf("\"error_code\":%d",v.code)) fmt.Println() fmt.Printf("第%d个测试用例:%s", k+1, v.desc) if v.showBody { fmt.Printf("接口返回%s", w.Body.String()) fmt.Println() } s := struct { Error_code int `json:"error_code"` Msg string `json:"msg"` Data interface{} `json:"data"` }{} err := tool.JsonToStruct([]byte(w.Body.String()), &s) assert.NoError(t, err) assert.Equal(t, v.errMsg, s.Msg, "错误信息不一致") assert.Equal(t, v.code, s.Error_code, "错误码不一致") } } |
使用子测试(subtests)
来控制嵌套测试和执行顺序、解决测试套件依赖性
func TestFoo(t *testing.T) { // <setup code> t.Run("A=1", func(t *testing.T) { ... }) t.Run("A=2", func(t *testing.T) { ... }) t.Run("B=1", func(t *testing.T) { ... }) // <tear-down code> } |
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理