Основы Golang — модульное тестирование и насмешки

Go

модульный тест Голанга

Введение в модульное тестирование

Чтобы гарантировать качество кода, многие компании требуют написания модульных тестов. Вот два индикатора,

  1. Покрытие функций: количество вызовов функций/количество функций, обычно 100%.
  2. Покрытие линии: количество пройденных линий/общее количество функций, обычно требуется > 60%

С помощью модульного тестирования мы можем тестировать различные сценарии, чтобы контролировать качество самих исследований и разработок. Компания, в которой я сейчас работаю, предъявляет высокие требования к модульному тестированию, и есть тенденция заменять тестирование.

go test

  • Тест go обычно использует xxx_test.go в качестве имени файла, а xxx не требует измерения имени файла.
  • TestMain как тест инициализации
  • Testxxx(t* testing.T)
  • go test для запуска модульных тестов
  • go test --v --test.run funcName может указать единственный метод для тестирования

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

.
├── client.go
├── client_test.go
func TestUser(t *testing.T) {
    var u = &User{Age: 15, Name: "alice"}
    if u.Age != 15 {
        t.Error("age err")
    }   
    if u.Name != "bob" {
        t.Error("name err")
    }   
}

Поскольку имя не соответствует ожиданиям, появится следующее приглашение

--- FAIL: TestUser (0.00s)
    client_test.go:28: name err
FAIL
exit status 1
FAIL	test/mocktest	0.005s

go convey

goconvey может очень хорошо поддерживать настройку и демонтаж, goconvey может выполнять инициализацию и уничтожение состояния перед запуском одного тестового примера. goconvey также имеет много определенных функций утверждения, которые можно использовать напрямую, и вы можете настроить функцию утверждения. Обычно используются следующие утверждения:

var (
    ShouldEqual          = assertions.ShouldEqual
    ShouldNotEqual       = assertions.ShouldNotEqual

    ShouldBeGreaterThan          = assertions.ShouldBeGreaterThan
    ShouldBeGreaterThanOrEqualTo = assertions.ShouldBeGreaterThanOrEqualTo
    ShouldBeLessThan             = assertions.ShouldBeLessThan
    ShouldBeLessThanOrEqualTo    = assertions.ShouldBeLessThanOrEqualTo
    ShouldBeBetween              = assertions.ShouldBeBetween
    ShouldNotBeBetween           = assertions.ShouldNotBeBetween
    ShouldBeBetweenOrEqual       = assertions.ShouldBeBetweenOrEqual
    ShouldNotBeBetweenOrEqual    = assertions.ShouldNotBeBetweenOrEqual
    ShouldContainSubstring    = assertions.ShouldContainSubstring
    ShouldNotContainSubstring = assertions.ShouldNotContainSubstring

    ShouldPanic        = assertions.ShouldPanic
    ShouldBeError      = assertions.ShouldBeError
)

Пример использования:

func TestUser(t *testing.T) {
    convey.Convey("TestUser", t, func() {
        var u = &User{Age: 15, Name: "alice"}
        convey.So(u.Age, convey.ShouldEqual, 15) 
        convey.So(u.Name, convey.ShouldEqual, "bob")
    })  
}

Поскольку имя не соответствует ожиданиям, появится следующее приглашение

  Line 30:
  Expected: 'bob'
  Actual:   'alice'
  (Should be equal)

mock

что такое насмешка

При модульном тестировании, что, если в процессе есть сторонние зависимости? Например, при выплате кредита требуется квота пользователя, а информация о квоте существует в другом микросервисе и должна быть извлечена с помощью RPC. Чтобы решить такой сценарий, мы можем использовать mock таким образом. Проще говоря, макет — это ввод, который может указывать зависимый интерфейс. Вывод можно понимать как фиксированные данные, вставленные заранее, чтобы процесс мог работать нормально.

Насмешка с насмешкой

предел:

  • Вы можете только издеваться над интерфейсом

руководство

  • Установите mockery: зайдите на github.com/vektra/mockery/.../
  • mockery -name=имя интерфейса, сгенерировать каталог моков.

определение интерфейса rpc, реализация интерфейса

client.go:


package rpc 

//go:generate mockery -name=Client

type Client interface {
    Get(key string) (data interface{}, err error)
}

type ClientImpl struct {
    Ct Client
}

func (p *ClientImpl) Get(key string) (data interface{}, err error) {
    if mockCondition  {
        return p.Ct.Get(key)
    }
    // real logic
}

client_test.go

package rpc 

import (
    "fmt"
    "test/mocktest/mocks"
    "testing"
)

type User struct {
    Age  int 
    Name string
}

func TestMock(t *testing.T) {
    convey.Convey("TestMock", t, func() {
        mc := &mocks.Client{}
        var u = &User{Age: 15, Name: "alice"}
        mc.On("Get", "alice").Return(u, nil)
        ci.Ct = mc
        data, err := ci.Get("alice")
        convey.So(data.Age, convey.ShouldEqual, 15)
    }
}