Это третье руководство из серии руководств по модульному тестированию языка Go с нуля. В нем рассказывается, как использовать макетный интерфейс инструментов gomock и gostub и заглушки в модульном тестировании.
В предыдущей статье «Пройдите одиночный тест серии 3 — тест базы данных»,Мы рассмотрели, как использоватьgo-sqlmock
иminiredis
Инструменты для тестирования баз данных。 Помимо внешних зависимостей, таких как сети и базы данных, при разработке мы часто используем различные типы интерфейсов. В этой статье будет приведен пример, демонстрирующий, как имитировать типы интерфейсов и как выполнять укладку при написании модульных тестов.
Пример кода «Пройдите одиночный тест от нуля до скользкой серии» загружен на Github.,Нажмите👉🏻https://github.com/go-quiz/golang-unit-test-demo Посмотреть полный исходный код.
gomockдаGoОфициальная система тестирования,Его можно легко использовать во встроенном пакете тестирования или в других средах. Мы используем его, чтобы имитировать эти типы интерфейсов в коде.,Легко писать тестирование。
Интернет-библиотеки с открытым исходным кодом обновляются и повторяются относительно быстро.,Рекомендуется непосредственно проверить официальную документацию.:https://github.com/golang/mock
Сначала вам нужно убедиться, что ваш$GOPATH/bin
Уже добавлено в переменные среды。
Goномер версии<1.16час:
GO111MODULE=on go get github.com/golang/mock/mockgen@v1.6.0
GoВерсия>=1.16час:
go install github.com/golang/mock/mockgen@v1.6.0
Если установка находится в вашем конвейере CI, вам потребуется соответствующая версия установки, соответствующая вашей среде CI.
mockgen
Существует два режима работы: режим источника и режим отражения.
Режим исходного код на основе исходного файла макета интерфейса. Это делается с помощью -source
Флаг включен. Другие флаги, которые могут быть полезны в этом режиме: -imports
и -aux_files
。
Например:
mockgen -source=foo.go [other options]
отражающий режимMock интерфейс путем создания программ, которые используют отражение для понимания интерфейса. Это включается путем передачи двух параметров без флагов: пути импорта и списка символов, разделенных запятыми. Можно использовать "." относится к пакету текущего пути.
Например:
mockgen database/sql/driver Conn,Driver
# Convenient for `go:generate`.
mockgen . Conn,Driver
mockgen
Команда используется для создания исходного кода фиктивного класса для данного исходного файла Go, содержащего имитируемый интерфейс. Он поддерживает следующие флаги:
-source
:Содержать доmockизинтерфейсиздокумент。-destination
:генерироватьизисточниккодписатьиздокумент。Если это не установлено,Код будет выведен на стандартный вывод.-package
:用于генерироватьизмоделирование类источниккодизимя пакета。Если это не установленоимя пакета默认在原имя пакета前添加mock_
префикс。-imports
:在генерироватьизисточниккодиспользуется виз Явный список импорта。Значениеfoo=bar/bazформаизчерез запятуюизсписок элементов,где bar/baz — импортируемый пакет,foo — это идентификатор, который будет использоваться для пакета в сгенерированном исходном коде.-aux_files
:Нужна ссылка для решенияиздополнительныйдокументсписок,Например, встроенный интерфейс, определенный в разных файлах. Указанное значение должно представлять собой список элементов, разделенных запятыми, в форме foo=bar/baz.go.,где bar/baz.go — исходный файл,fooда-source
документиспользоватьиздокументизимя пакета。-build_flags
:(толькоотражающий режим) передать флаг, чтобы перейти к дословному переводу build-mock_names
:генерироватьизмоделированиеиз Настроитьимясписок。Это указанодляодинчерез запятуюизсписок элементов,ФормаRepository = MockSensorRepository,Endpoint=MockSensorEndpoint
,вRepository
даинтерфейсимя,mockSensorrepository
данеобходимыйизmockимя(mockфабричный методиmockРегистратор будетmockимя)。если один изинтерфейс没有指定Настроитьимя,Будет использовано соглашение об именах по умолчанию.-self_package
:генерироватьизкодиз Полный путь импорта пакета。используйте этоflagизглазиздапытаясь сдержать себяиз包Приходить防止генерироватькодсерединаиз Круговой импорт。еслиmockиз Пакет установлендляэтоизвход(в целомдаосновной вход),и вывод — stdio,Тогда Mockgen не может обнаружить окончательный выходной пакет.,Это произойдет. Установка этого флага сообщит макету о том, какая импортная нестабильность.-copyright_file
:用于将авторское право标头添加到генерироватьизисточниккодсерединаизавторское праводокумент-debug_parser
:только Распечатать результаты парсера-exec_only
:(отражающий режим) Если установлено, выполнить эту программу отражения-prog_only
:(отражающий режим) только генерирует программу отражения, записывает ее в стандартный вывод и завершает работу.-write_package_comment
:еслидляtrue,затем напишите комментарий к документации пакета (годок) (по умолчанию верно).Здесь мы возьмем операции с базой данных, часто используемые в повседневной разработке, в качестве примера, чтобы объяснить, как использовать gomock для модульного тестирования макетного интерфейса.
Предположим, что бизнес-код для запроса базы данных MySQL выглядит следующим образом:,вDB
даодин Настроитьизинтерфейстип:
// db.go
// DB данныеинтерфейс
type DB interface {
Get(key string)(int, error)
Add(key string, value int) error
}
// GetFromDB Функция запроса данных из БД на основе ключа
func GetFromDB(db DB, key string) int {
if v, err := db.Get(key);err == nil{
return v
}
return -1
}
Мы сейчас собираемсяGetFromDB
Написание функций Модульное обновляем код, но не можем в Модульном Процесс тестирования для подключения к реальной базе данных, на этот раз вам понадобится макет DB
этотинтерфейсоблегчить Модульное тестирование。
Используйте вышеупомянутое mockgen
инструмент Приходитьдлягенерировать相应изmockкод。Выполнив следующие действияиз Заказ,Мы можем создать его в рамках текущего проектаmocks
папка,Внутри хранится одинdb_mock.go
документ。
mockgen -source=db.go -destination=mocks/db_mock.go -package=mocks
db_mock.go
документсерединаиз СодержаниедаmockСвязанныйинтерфейсизкод Понятно。
Обычно нам не нужно его редактировать.,Просто используйте их установленным образом в Модульном тестировании. Например,мы пишемTestGetFromDB
Функция следующая:
// db_test.go
func TestGetFromDB(t *testing.T) {
// Создайте контроллер gomock для записи информации о последующих операциях.
ctrl := gomock.NewController(t)
// утверждение Ожидаемые методы выполняются
// Больше нет необходимости вручную вызывать этот метод в одном тесте Go1.14+.
defer ctrl.Finish()
// Вызовите метод NewMockDB в коде, сгенерированном макетом.
// Здесь «mocks» — это имя пакета, указанное при генерации кода.
m := mocks.NewMockDB(ctrl)
// заглушка
// Если в функцию Get передан параметр liwenzhou.com, возвращается 1nil.
m.
EXPECT().
Get(gomock.Eq("liwenzhou.com")). // параметр
Return(1, nil). // возвращаемое значение
Times(1) // Количество звонков
// При вызове функции GetFromDB передайте приведенный выше фиктивный объект m.
if v := GetFromDB(m, "liwenzhou.com"); v != 1 {
t.Fatal()
}
}
Заглушка при тестировании программного обеспечения означает замену целевого кода некоторым кодом (заглушкой), который обычно используется для защиты или дополнения ключевых кодов в бизнес-логике для облегчения модульного тестирования.
Экранирование: я не хочу использовать в модульных тестах тяжелые ресурсы, такие как соединения с базой данных. Завершение: Зависимые вышестоящие и нижестоящие функции или методы еще не реализованы.
В приведенном выше коде используется свая,когда вошелGet
функцияизпараметрдляliwenzhou.com
час Просто вернись1, nil
извозвращаемое значение。
gomock
Поддержкапараметр、возвращаемое значение、Количество звонков、Последовательность звонков и другие проводили свайные работы.
Связанное использование параметра включает: - gomock.Eq(value): представляет параметр, эквивалентный значению. - gomock.Not(value): параметр, представляющий значение, не являющееся значением. - gomock.Any(): параметр, представляющий любое значение. - gomock.Nil(): параметр, представляющий нулевое значение. - SetArg(n, значение): установите значение n-го (начиная с 0) параметра, обычно используемого для указателей или срезов.
Конкретные примеры заключаются в следующем:
m.EXPECT().Get(gomock.Not("q1mi")).Return(10, nil)
m.EXPECT().Get(gomock.Any()).Return(20, nil)
m.EXPECT().Get(gomock.Nil()).Return(-1, nil)
Давайте поговорим об этом отдельно здесьSetArg
из Применимые сценарии,Предположим, у вас есть программа, которую нужно имитировать следующим образом:
type YourInterface {
SetValue(arg *int)
}
в это время,штабелированиеизчас候就可以использоватьSetArg
изменитьпараметризценить。
m.EXPECT().SetValue(gomock.Any()).SetArg(0, 7) // Установите первый параметр SetValue равным 7.
Обычаи, связанные с возвращаемым значением в gomock, следующие:
Например:
m.EXPECT().Get(gomock.Any()).Return(20, nil)
m.EXPECT().Get(gomock.Any()).Do(func(key string) {
t.Logf("input key is %v\n", key)
})
m.EXPECT().Get(gomock.Any()).DoAndReturn(func(key string)(int, error) {
t.Logf("input key is %v\n", key)
return 10, nil
})
Методы, имитируемые с помощью инструмента gomock, будут вызываться ожидаемое количество раз. По умолчанию каждый метод можно вызывать только один раз.
m.
EXPECT().
Get(gomock.Eq("liwenzhou.com")). // параметр
Return(1, nil). // возвращаемое значение
Times(1) // Установите метод Get для ожидания количества звонковдля1
// При вызове функции GetFromDB передайте приведенный выше фиктивный объект m.
if v := GetFromDB(m, "liwenzhou.com"); v != 1 {
t.Fatal()
}
// При повторном вызове метода Get из макета выше он не удовлетворяет требованию количества. звонков - 1 ожидание
if v := GetFromDB(m, "liwenzhou.com"); v != 1 {
t.Fatal()
}
gomock предоставляет нам следующий метод для установки ожидаемого количества вызовов.
Times()
утверждение Mock Сколько раз вызывался метод.MaxTimes()
Максимальное количество раз.MinTimes()
Минимальное количество раз.AnyTimes()
любое количество раз (в том числе 0 Второсортный).gomockОн также поддерживает использованиеInOrder
Спецификация методаmockметодиз Последовательность звонков:
// Укажите заказ
gomock.InOrder(
m.EXPECT().Get("1"),
m.EXPECT().Get("2"),
m.EXPECT().Get("3"),
)
// Вызывается последовательно
GetFromDB(m, "1")
GetFromDB(m, "2")
GetFromDB(m, "3")
Также известныйизGoтестовая библиотекаtestifyглаз前также提供类似изmockинструмент—testify/mock
иmockery
。
GoStubтакжедаодин Модульное Инструмент накопления в тестировании поддерживает накопление глобальных переменных, функций и т. д.
Однако лично я считаю, что это не очень удобно для объединения функций. Обычно я использую его только для объединения глобальных переменных в модульных тестах.
go get github.com/prashantv/gostub
Здесь мы используем пример кода из официальной документации, чтобы продемонстрировать, как использовать gostub для заглушки глобальных переменных.
// app.go
var (
configFile = "config.json"
maxNum = 10
)
func GetConfig() ([]byte, error) {
return ioutil.ReadFile(configFile)
}
func ShowNumber()int{
// ...
return maxNum
}
Приведенный выше код определяет две глобальные переменные и две функции, использующие глобальные переменные. Теперь мы напишем Модульное тестирование для этих двух функций.
// app_test.go
import (
"github.com/prashantv/gostub"
"testing"
)
func TestGetConfig(t *testing.T) {
// Сложите глобальную переменную configFile и назначьте ей указанный файл.
stubs := gostub.Stub(&configFile, "./test.toml")
defer stubs.Reset() // Сброс после тестирования
// Ниже приведен код теста
data, err := GetConfig()
if err != nil {
t.Fatal()
}
// Содержимое возвращаемых данных — это содержимое файла /tmp/test.config, указанного выше.
t.Logf("data:%s\n", data)
}
func TestShowNumber(t *testing.T) {
stubs := gostub.Stub(&maxNum, 20)
defer stubs.Reset()
// Вот тестовый код
res := ShowNumber()
if res != 20 {
t.Fatal()
}
}
Выполните модульный тест и просмотрите результаты:
❯ go test -v
=== RUN TestGetConfig
app_test.go:18: data:blog="liwenzhou.com"
--- PASS: TestGetConfig (0.00s)
=== RUN TestShowNumber
--- PASS: TestShowNumber (0.00s)
PASS
ok golang-unit-test-demo/gostub_demo 0.012s
Из приведенного выше примера мы видим, что в Модульном тестированиеиспользуется вgostub
может быть очень удобноиз对全局变量进行штабелирование,Приведите его к нашему ожидаемому значению для тестирования.
Написание кода для ежедневной работы Модульное тестированиечас Как бороться скодсерединаизинтерфейстипда Очень частоизвопрос,В этой статье объясняется, как использоватьgomock
mockСвязанныйинтерфейси Как использоватьgostub
инструмент对全局变量进行штабелирование。
в следующей статье,мы пойдем дальше,Подробная информация о том, как написать Модульное тестированиечасиспользовать更全能изштабелированиеинструмент——monkey
。