The introduction

  • Learn about go unit testing
  • Why write unit tests
  • How should unit tests be written in the GIN framework
  • How does GIN write elegant unit tests

1. Unit testing knowledge

  • The unit test file name must be xxx_test.go(where XXX is the business logic program)

  • It is divided into basic test, benchmark test and case test

  • The function name for the underlying test must be Testxxx(XXX can be used to identify business logic functions)

  • The benchmark must appear as the BenchmarkXXX function name

  • The case test must come out with the ExampleXXX function name

  • Unit test function parameter must be T *testing. t (testing framework strong requirement)

  • Test and test files in a package (I’ll put tests in a separate directory)

  • Test case files do not participate in normal source compilation and are not included in the executable

  • You must import the testing package

  • Common commands have many people said, I will provide a document

2. Why write unit tests

Ensure the quality of the code, ensure that every function is running, the results are correct, and ensure that the performance of the written code is good. Using unit tests at the same time is intuitive and can improve development efficiency. You don’t need to compile your code every time, and then go to Postman to simulate parameter tests. It also helps you with performance testing and code coverage, export test reports, and more

How to write unit tests in the GIN framework

TestMain

When writing tests, you sometimes need to do extra setup or teardown before or after the test. Sometimes, tests also need to control the code that runs on the main thread. To support these requirements, the Testing package provides the TestMain function:

package tests

import (
	"testing"
	"fmt"
	"os"
	
	"github.com/gin-gonic/gin"
	
	"go-api/config"
)

func setup(a) {
	gin.SetMode(gin.TestMode)
	fmt.Println(config.AppSetting.JwtSecret);
	fmt.Println("Before all tests")}func teardown(a) {
	fmt.Println("After all tests")}func TestMain(m *testing.M)  {
	setup()
    fmt.Println("Test begins....")
	code := m.Run() // If this sentence is not added, only Main is executed
	teardown()
	os.Exit(code)
}
Copy the code

Gin unit test case


package main

func setupRouter(a) *gin.Engine {
	r := gin.Default()
	r.GET("/ping".func(c *gin.Context) {
		c.String(200."pong")})return r
}

func main(a) {
	r := setupRouter()
	r.Run(": 8080")}Copy the code
package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

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

func TestPingRoute(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())
}
Copy the code

4. How can GIN write elegant unit tests

Use the 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)
        ifactual ! = tt.expected { t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected)
        }
    }
}
Copy the code

Encapsulate some Test Helpers

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         / / status code
	param        string      / / parameters
	method       string      // Request type
	desc         string      / / description
	showBody     bool        // Whether to show returns
	errMsg       string      // Error message
	url          string      / / links
	content_type string      //
	ext1         interface{} // Customize 1
	ext2         interface{} // Customize 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 test case: %s", k+1, v.desc)
		if v.showBody {
			fmt.Printf("Interface returns %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, "Inconsistent error messages.")
		assert.Equal(t, v.code, s.Error_code, "Inconsistent error codes")}}Copy the code

Using subtests

To control the sequence of nested tests and execution and resolve test suite dependencies

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>
}
Copy the code

5. Go test command

  • -bench regexp performs corresponding benchmarks, such as -bench=.
  • -benchtime s Specifies the performance test time. The default time is one second
  • -cover Opens the test coverage;
  • -run regexp only runs functions that match regexp. For example, -run=Array then executes functions that start with Array;
  • -v Displays detailed test commands.
  • -cache=1 Removes the cache
  • -args brings the parameters following -args to the test
  • -json Converts the output to JSON
  • The -o -o argument specifies the generated binary executable and executes the test without removing the program at the end of the test
  • -count Specifies the number of execution times. The default one

-bench

func BenchmarkMakeSliceWithoutAlloc(b *testing.B)
func BenchmarkMakeSliceWithPreAlloc(b *testing.B)
func BenchmarkSetBytes(b *testing.B)
Copy the code

-agrs


func TestArgs(t *testing.T) {
    if! flag.Parsed() { flag.Parse() } argList := flag.Args()// flag.args () returns all arguments following -args as slices, with each element representing one argument
    for _, arg := range argList {
        if arg == "cloud" {
            t.Log("Running in cloud.")}else {
            t.Log("Running in other mode.")}}Copy the code

6, the end

Let me share with you

Common commands and document address portal

🏆 technology project phase ii | and I Go those things…