[модульное тестирование gin действительно ароматная практика | 🏆 техническая тема 2-й выпуск]

Go

введение

  • Узнайте о здравом смысле модульного тестирования go
  • Зачем писать модульные тесты
  • Как писать юнит-тесты в джин-фреймворке
  • Как элегантно писать модульные тесты в джине

1. Здравый смысл модульного тестирования

  • Имя файла модульного теста должно быть xxx_test.go (где xxx — программа бизнес-логики).

  • Делится на базовый тест, эталонный тест, кейс-тест

  • Имя функции базового теста должно быть Testxxx (xxx может использоваться для идентификации функций бизнес-логики).

  • Контрольные показатели должны отображаться как имена функций BenchmarkXXX.

  • В кейс-тесте должно быть имя функции ExampleXXX вне строки.

  • Параметры функции модульного тестирования должны быть t *testing.T (требуется средой тестирования)

  • Тестовая программа и тестируемые программные файлы находятся в пакете (можете не трогать, я положу отдельный каталог с тестами)

  • Файлы тестовых случаев не будут участвовать в обычной компиляции исходного кода и не будут включены в исполняемые файлы.

  • Этот пакет должен быть импортирован и протестирован

  • Многие люди сказали общие команды, я предоставлю документ

2. Зачем писать модульные тесты

Убедитесь в качестве кода, убедитесь, что каждая функция работает, результат работы правильный, убедитесь, что производительность написанного кода хорошая, а использование модульных тестов очень интуитивно понятно для повышения эффективности разработки, вам не нужно компилировать свой кодируйте каждый раз, а затем переходите к почтальону для имитации тестирования параметров, модуль может имитировать http-запросы и может помочь вам выполнить тестирование производительности и покрытие кода, экспортировать отчеты о тестировании и т. д.

3. Как писать юнит-тесты во фреймворке gin

TestMain

При написании тестов иногда требуется дополнительная настройка или демонтаж до или после теста; иногда тест также должен контролировать код, работающий в основном потоке. Для поддержки этих потребностей пакет тестирования предоставляет функцию 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)
}

тестовый пример джин-юнит


package main

func setupRouter() *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"
)

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())
}

4. Как элегантно писать юнит-тесты в джине

Использование табличных тестов

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)
        }
    }
}

Инкапсулировать несколько тестовых помощников

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, "错误码不一致")
	}
}

Использование подтестов

для управления вложенными тестами и порядком выполнения, разрешения зависимостей набора тестов

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>
}

5. перейти к тестовой команде

  • -bench regexp выполняет соответствующие тесты, например -bench=.;
  • -benchtime s указывает время теста производительности, по умолчанию одна секунда
  • -крышка включает тестовое покрытие;
  • -run regexp запускает только те функции, которые соответствуют регулярному выражению, например -run=Array затем выполняет функции, содержащие начало Array;
  • -v Показать подробные команды для теста.
  • -cache=1 удалить кеш
  • -args приводить аргументы после -args к тесту
  • -json конвертировать вывод в json
  • Параметр -o -o указывает сгенерированную бинарную исполняемую программу и выполняет тест, программа не будет удалена после теста
  • -count указывает количество выполнений, по умолчанию 1

-bench

func BenchmarkMakeSliceWithoutAlloc(b *testing.B)
func BenchmarkMakeSliceWithPreAlloc(b *testing.B)
func BenchmarkSetBytes(b *testing.B)

-agrs


func TestArgs(t *testing.T) {
    if !flag.Parsed() {
        flag.Parse()
    }
 
    argList := flag.Args() // flag.Args() 返回 -args 后面的所有参数,以切片表示,每个元素代表一个参数
    for _, arg := range argList {
        if arg == "cloud" {
            t.Log("Running in cloud.")
        }else {
            t.Log("Running in other mode.")
        }
    }

6. Конец

Поделиться с тобой

Общие команды и документацияадресный портал

🏆 Технический спецвыпуск 2 | То, что я сделал с Go...