Перейти к содержимому

Что можно сказать про моки в go

  • автор:

Моки — JS: Продвинутое тестирование

В тестировании очень популярен мокинг. Технически он похож на стабинг, и из-за этого их часто путают (специально или ненамеренно). Но все же они служат разным целям и используются в разных ситуациях. Разберемся, что это такое, и когда он нужен.

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

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

HTTP

import nock from 'nock'; import  getPrivateForkNames > from '../src.js'; // Предотвращение случайных запросов nock.disableNetConnect(); test('getPrivateForkNames', async () =>  // Полное название домена const scope = nock('https://api.github.com') // Полный путь .get('/orgs/hexlet/repos/?private=true') .reply(200, [ fork: true, name: 'one' >,  fork: false, name: 'two' >]); await getPrivateForkNames('hexlet'); // Метод `scope.isDone()` возвращает `true` только тогда, // когда соответствующий запрос был выполнен внутри `getPrivateForkNames` expect(scope.isDone()).toBe(true); >); 

Это и называется мокинг. Мок проверяет, что какой-то код выполнился определенным образом. Это может быть вызов функции, HTTP-запрос и тому подобное. Задача мока убедиться в том, что это произошло, и в том, как конкретно это произошло, например, что в функцию были переданы конкретные данные.

Что дает нам такая проверка? В данном случае — мало что. Да, мы убеждаемся, что вызов был, но само по себе это еще ни о чем не говорит. Так когда же полезны моки?

Представьте, что мы бы разрабатывали библиотеку @octokit/rest, ту самую, что выполняет запросы к GitHub API. Вся суть этой библиотеки в том, чтобы выполнить правильные запросы с правильными параметрами. Поэтому там нужно обязательно проверять выполнение запросов с указанием точных URL-адресов. Только в таком случае можно быть уверенными, что она выполняет верные запросы.

В этом ключевое отличие мока от стаба. Стаб устраняет побочный эффект, чтобы не мешать проверке результата работы кода, например, возврату данных из функции. Мок фокусируется на том, как конкретно работает код, что он делает внутри. При этом чисто технически мок и стаб создаются одинаково, за исключением того, что на мок вешают ожидания, проверяющие вызовы. Это приводит к путанице, потому что часто моками называют стабы. С этим ничего уже не поделать, но для себя всегда пытайтесь понять, о чем идет речь. Это важно, от этого зависит фокус тестов.

Функции

Моки довольно часто используют с функциями (методами). К примеру, они могут проверять:

  • Что функция была вызвана
  • Сколько раз она была вызвана
  • Какие аргументы мы использовали
  • Сколько аргументов было передано в функцию
  • Что вернула функция

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

[1, 2, 3].forEach((v) => console.log(v)); // или проще [1, 2, 3].forEach(console.log) 

Эта функция ничего не возвращает, поэтому напрямую ее не протестировать. Можно попробовать сделать это с помощью моков. Проверим, что она вызывает переданный колбек и передает туда нужные значения.

Так как мы изучаем Jest, то для создания моков воспользуемся встроенным механизмом Jest. В других фреймворках могут быть свои встроенные механизмы. Кроме того, как мы убедились выше, существуют специализированные библиотеки для моков и стабов.

test('forEach', () =>  // Моки функций в Jest создаются с помощью функции jest.fn // Она возвращает функцию, которая запоминает все свои вызовы и переданные аргументы // Потом это используется для проверок const callback = jest.fn(); [1, 2, 3].forEach(callback); // Теперь проверяем, что она была вызвана с правильными аргументами нужное количество раз expect(callback.mock.calls).toHaveLength(3); // Первый аргумент первого вызова expect(callback.mock.calls[0][0]).toBe(1); // Первый аргумент второго вызова expect(callback.mock.calls[1][0]).toBe(2); // Первый аргумент третьего вызова expect(callback.mock.calls[2][0]).toBe(3); >); 

С помощью моков мы проверили, что функция была вызвана ровно три раза, и ей, последовательно для каждого вызова, передавался новый элемент коллекции. В принципе, можно сказать, что этот тест действительно проверяет работоспособность функции forEach() . Но можно ли сделать это проще, без мока и без завязки на внутреннее поведение? Оказывается, можно. Для этого достаточно использовать замыкание:

test('forEach', () =>  const result = []; const numbers = [1, 2, 3]; numbers.forEach((x) => result.push(x)); expect(result).toEqual(numbers); >); 

Объекты

Jest позволяет создавать моки и для объектов. Они создаются с помощью функции jest.spyOn() , напоминающей уже известную нам jest.fn() . Эта функция принимает на вход объект и имя метода в этом объекте, и отслеживает вызовы этого метода. Отследим, в качестве примера, вызов console.log() :

test('logging something', () =>  const spy = jest.spyOn(console, 'log'); console.log(12); expect(spy).toHaveBeenCalled(); // => true, т.к. метод log был вызван expect(spy).toHaveBeenCalledTimes(1); // => true, т.к. метод был вызван 1 раз expect(spy).toHaveBeenCalledWith(12); // true, т.к. log был вызван с аргументом 12 expect(spy.mock.calls[0][0]).toBe(12); // проверка, идентичная предыдущей >); 

Кроме того, Jest позволяет создавать свою реализацию сбора данных о вызовах отслеживаемой функции при помощи метода mockImplementation(fn) . Колбэком этого метода будет функция, которая выполняется после каждого вызова отслеживаемой функции. Возьмем предыдущий пример, но теперь соберем аргументы каждого вызова console.log() в отдельный массив:

test('logging something', () =>  const logCalls = []; // Здесь функция внутри mockImplementation принимает на вход аргумент(ы), // с которым был вызван console.log, и сохраняет его в заранее созданный массив const spy = jest.spyOn(console, 'log').mockImplementation((. args) => logCalls.push(args)); console.log('one'); console.log('two'); expect(logCalls.join(' ')).toBe('one two'); >); 

Преимущества и недостатки

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

  • После рефакторинга приходится переписывать тесты (много тестов!), даже если код работает правильно. Происходит это из-за завязки на то, как конкретно работает код

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Что можно сказать про моки в go

[2023] 如何获得绿色信任系数 ✅

1. 使用Steam Guard 。 2. 将您的个人资料公开。 3. 成为Steam社区的活跃成员。 4. 经常玩游戏,不作弊。 5. 得到队友的表扬(赞扬)。 6. 不要把任何人踢出游戏。 7. 不要阻挡你的队友,不要在比赛中伤害你的队友。 8. 多做OVERWATCH案件。 9. 给这样的手册打好分 10. 添加更多的朋友。 11. 对许多不同的资料、讨论和像这样的指南进行评论。 12. 如果你喜欢,你可以给奖。 .

How to Disable Build Number in the left corner✅
In this guide i’ll show how to disable build number in CS2 .

How to Play with all Knives in CS2 ✅
In this guide i’ll show you how to play with all knifes in CS2! .

[NEW]How to control spray in CS2✅
In this guide i’ll show you how to control spray of every weapon in CS2! .

Как поиграть со Всеми Ножами в CS2✅
В этом руководстве я покажу вам, как поиграть со всеми ножами в CS2 .

Спреи и разброс всего оружия в CS2 ✅
Сегодня я покажу вам спреи всех оружий в CS2. Заучив их, вы будете стрелять не хуже Симпла! .

How to Play CS:GO After CS2 Release ✅
In this guide i will show you how to play CS:GO even after CS2 release! .

How to make foosteps louder in CS2✅
In this guide i’ll show you how to make footsteps louder! .

How to input CS:GO config into CS2✅
In this guide i’ll show you how to run CS:GO config in CS2! .

How to Disable Animated Avatars in CS2✅
In this guide I will show you how to disable Animated Avatars in Counter-Strike 2! .

[CS2] How to get Green Trust Factor ✅

1. Use Steam Guard. 2. Make your profile public. 3. Become an active member of the Steam Community. 4. Play games often without cheating. 5. Get praise (commended) by your teammates. 6. Do not kick anyone out of the game. 7. Do not block your team.

Как поиграть в CS:GO после Релиза CS2 ✅
В этом гайде я покажу, как поиграть в CS:GO после релиза CS2 .

Как увеличить ФПС в CS2? ✅
В этом гайде я покажу вам, как без каких-либо программ увеличить ФПС и убрать лаги в CS2! .

Как Отключить Анимированные Аватары в CS2✅
В этом руководстве я покажу вам, как отключить анимированные аватарки в CS2 .

[2023] Как получить зелёный фактор доверия в CS2✅

1. Включи Steam Guard. 2. Сделай свой профиль общедоступным. 3. Будь активным пользователем Steam (наслаждайтесь руководствами ,участвуйте в дискуссиях,делайте скриншоты). 4. Играйте в игры без использования читов (типа wallhack,aimbot). 5. Получай .

ВСЕ КОНСОЛЬНЫЕ КОМАНДЫ CS2

Всем привет! Собрал для вас самые полезные команды для нашей любимой игры CS2 Спасибо за внимание. Подпишитесь.

[CS2 SYSTEM] How to get Green Trust Factor

1. Have Prime status. 2. Turn on Steam Guard. 3. Make your profile public. 4. Be an active Steam user (enjoy guides, participate in discussions, take screenshots). 5. Play games without using cheats (such as wallhack, aimbot). 6. Get recommendations f.

How to become happy | Как стать счастливым

In this guide I will tell you how to become happy | В этом руководстве я расскажу вам, как стать счастливым.

Увеличение FPS в Counter-Strike 2
作者: Mineteen Milkcoin
Оптимизируем и увеличиваем FPS в Counter-Strike 2.

[СИСТЕМА CS2] Как получить фактор зеленого доверия

1. Иметь Прайм-статус. 2. Включите Steam Guard. 3. Сделайте свой профиль общедоступным. 4. Будьте активным пользователем Steam (наслаждайтесь руководствами, участвуйте в обсуждениях, делайте скриншоты). 5. Играйте в игры без использования читов (типа w.

How to Rank Up Faster

1. Have logged a lot of hours in the last two weeks. 2. Have the Green Trust Factor. 3. Do not receive reports. 4. Be top 3 in terms of damage in the score table. 5. Don’t kick anyone for no apparent reason in the game. 6. Don’t bother your game mates.

Как стать счастливым
Тут будет рассказано, как стать счастливым.

Как прокачать навык стрельбы в CS 2

Руководство по улучшению навыков стрельбы в игре «Counter-Strike 2» может быть полезным как для начинающих игроков, так и для тех, кто хочет усовершенствовать свои навыки. Вот шаги и советы, которые могут помочь вам стать более точным стрелком.

Карты и тактики

Руководство по картам и тактике в игре «Counter-Strike 2» (CS:GO, CS 1.6 или другой версии) может помочь вам и вашей команде добиваться лучших результатов. Вот некоторые советы от меня.

[RU] Самые полезные команды в Counter Strike 2

Самые полезные команды в Counter Strike 2 на русском языке, Буду добавлять в будущем ещё, что-нибудь.

Как работает система дропа в CS 2?

Я решил рассказать тему еженедельного дропа предметов предметов в CS 2. В этом статье я подробно расскажу о шансах на еженедельный дроп.В данной статье будет максимально кратко рассказана,что как и почему работает дроп в CS 2.Я надеюсь,что данная статья ва.

Тестирование в GO с использованием «моков»

Важной частью создания програмного обеспечения является покрытие кодовой базы тестами.

В основании всем известной пирамиды тестрования лежат юнит тесты. Не будем с ней спорить, а будем писать юниты, много юнит тестов. Не всегда можно просто взять и написать тест, иногда для тестирования нам нужно заглушить какие-то зависимости, будь то доступ в базу данных, сетевое взаимодействие или какая-нибудь магия.

Естественно, для этого придумали «моки». Придумали не только их, но в данной статье речь пойдет только про моки и немного про кодогенерацию, не писать же их руками.

У нас есть некоторый оъект, у которого есть внешняя зависимость. Внешнюю зависимость мы подсовывем при инстанцировании, не вызываем её непонятно откуда, как минимум, это не красиво. Также мы используем интерфейс для внешней зависимости.

type Action struct  s interfaces.ServiceInterface > func NewAction(s interfaces.ServiceInterface) Action  return Actions: s> > func (a Action) Make(s string) int  i, err := a.s.SomeAction(s) if err != nil  return 1 > return i >

Интерфейс очень простой. В нём один метод, который делает что-то.

type ServiceInterface interface  SomeAction(string) (int, error) >

Зачем он нужен? Как минимум писать интерфейсы — это хорошо. Но, в данном случае, на его основе мы будем генерировать mock.

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

type Service struct> func (s Service) SomeAction(str string) (int, error)  if str == "a"  return 100, nil > return 0, errors.New("error") >

Все готово, можно тестировать. По-хорошему бы начать с тестов, но главное чтобы они вообще были, так что приступим.

func TestMain(t *testing.T)  tests := []struct  name string service interfaces.ServiceInterface inputString string want int >  name: "test with a input", service: internal.Service>, inputString: "a", want: 100, >,  name: "test with s input", service: internal.Service>, inputString: "s", want: 1, >, > for _, tt := range tests  t.Run(tt.name, func(t *testing.T)  s := internal.NewAction(tt.service) got := s.Make(tt.inputString) if tt.want != got  t.Errorf("Action() got = %v, want %v", got, tt.want) > >) > >

Тесты проходят. Посмотрев на код, мы видим, что поведение зависит от internal.Service<> . Нашей внешней зависимости. Зависит достаточно прямолинейно, но это код только для статьи.
Возможно, что реализация былабы куда сложнее и тянула за собой другие зависимости.

Уберем инстанс internal.Service<> из тестов, заменим на другую реализацию ServiceInterface . Для этого будем использовать пакет github.com/golang/mock/gomock. Делается это довольно просто.

Так как go умеет писать код за нас, пусть пишет. Дописываем к нашему интерфейсу строку для go generate и получаем следующий код:

//go:generate mockgen -source=service.go -destination=../internal/mock/service.go -package=mock type ServiceInterface interface  SomeAction(string) (int, error) >

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

Так же нам нужно поставить mockgen, сделать это тоже просто.

go install github.com/golang/mock/mockgen
go generate interfaces/service.go

Модифицируем наши тесты. Напишем функцию для создания мока сервиса simulateService, чтобы на лету менять ожидания от него.

func TestMain(t *testing.T)  controller := gomock.NewController(t) tests := []struct  name string service interfaces.ServiceInterface inputString string want int >  name: "test with error but with a", service: simulateService(controller, 100, errors.New("error")), inputString: "a", want: 1, >,  name: "test without error", service: simulateService(controller, 500, nil), inputString: "s", want: 500, >, > for _, tt := range tests  t.Run(tt.name, func(t *testing.T)  s := internal.NewAction(tt.service) got := s.Make(tt.inputString) if tt.want != got  t.Errorf("Action() got = %v, want %v", got, tt.want) > >) > > func simulateService(controller *gomock.Controller, res int, err error) interfaces.ServiceInterface  mockService := mock.NewMockServiceInterface(controller) mockService.EXPECT().SomeAction(gomock.Any()).Return(res, err).AnyTimes() return mockService >

Теперь наш сервис ведёт себя по другому. Входные данные теперь просто gomock.Any(), так как нам не важно что придёт на вход, а выходными мы управляем через .Return(). Вот и весь «секрет».

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

Записки программиста

На первый взгляд, модульные тесты в Go пишутся очень просто. Создаем файл с именем пакет_test.go , в нем объявляем функции с именами TestЧтоТестируем , говорим go test , ну и считай готово. Однако на деле все оказывается чуточку сложнее. Например, оказывается, что из коробки в языке нет ни ассертов, ни моков. А когда ты начинаешь генерировать моки, они внезапно начинают участвовать в подсчете покрытия кода тестами. В общем, давайте разберемся, как все устроено на самом деле.

Тестируемый код

Как и в статье про сериализацию, тестировать будем героев для какой-нибудь RPG. Поскольку на этот раз сериализовать мы ничего не будем, перепишем немного код, воспользовавшись типичной для Go реализацией типов-сумм через interface<> :

type Hero struct <
Name string
HP int
XP int
info interface <> // *WarriorInfo or *MageInfo
>

Примечание: Как альтернативный вариант, мы могли бы сказать, что Hero — это интерфейс, а Warrior и Mage являются совершенно независимыми структурами, реализующими данный интерфейс. Преимущество такого подхода заключается в том, что он позволяет избежать использования type switches, благодаря чему является более типобезопасным. Кроме того, при таком подходе в структурах используется меньше указателей. Минус подхода — некоторое распухание кода, связанное с тем, что для каждого экземпляра Hero требуется объявить все методы интерфейса, даже если реализация отдельно взятого метода у них одинакова и представляет собой обертку над одной и той же функцией. А чем больше кода, тем менее эффективно используются кэши CPU. Кроме того, поскольку Hero становится интерфейсом, все его методы будут вызываться через указатели, что ломает branch prediction. В общем и целом, у этих двух подходов есть свои слабые и сильные стороны.

Объявим немного вспомогательных методов:

// IsDead return true if hero has zero HP.
func ( h * Hero ) IsDead () bool <
return h . HP == 0
>

// IsWarrior return true if hero is a warrior.
func ( h * Hero ) IsWarrior () bool <
switch h . info . ( type ) <
case * WarriorInfo :
return true
default :
return false
>
>

// IsMage returns true if hero is a mage.
func ( h * Hero ) IsMage () bool <
switch h . info . ( type ) <
case * MageInfo :
return true
default :
return false
>
>

Реализуем метод Attack :

// Attack a given enemy
func ( h * Hero ) Attack ( enemy CanTakeDamage ) <
if h . IsMage () <
h . doMageAttack ( enemy )
> else if h . IsWarrior () <
h . doWarriorAttack ( enemy )
> else <
panic ( «unknown class» )
>
>

Атаковать можно не только других героев, но также мобов, предметы интерьера, и так далее. Поэтому аргументом методу передается не Hero , а интерфейс CanTakeDamage :

type CanTakeDamage interface <
TakeDamage ( num int ) int
>

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

func ( h * Hero ) doMageAttack ( enemy CanTakeDamage ) <
info := h . info . ( * MageInfo )
if info . Mana < = 5 <
// there is no enough mana
return
>

if len ( info . Spellbook ) == 0 <
// there are no known spells
return
>

info . Mana -= 5
h . TakeDamage ( enemy . TakeDamage ( 20 ))
>

Маги могут атаковать только при наличии маны и изученных заклинаний. Для простоты будем считать, что все заклинания наносят 20 очков урона и стоят 5 единиц маны.

func ( h * Hero ) doWarriorAttack ( enemy CanTakeDamage ) <
info := h . info . ( * WarriorInfo )
if info . Weapon == BOW <
if info . ArrowsNumber > 0 <
// attack using a bow
h . TakeDamage ( enemy . TakeDamage ( 12 ))
info . ArrowsNumber —
>
> else if info . Weapon == SWORD <
h . TakeDamage ( enemy . TakeDamage ( 8 ))
> else <
panic ( «unknown weapon» )
>
>

Воин, вооруженный луком, может атаковать только при наличии стрел. Мечем можно атаковать всегда. Стрелы всегда наносят 12 единиц урона, а меч — 8 единиц.

// TakeDamage takes the damage and returns the damage
// an attacker should take (e.g. because of applied spells)
func ( h * Hero ) TakeDamage ( num int ) int <
h . HP -= num
if h . HP < 0 <
h . HP = 0
>

if ( h . IsMage ()) <
// all mages are always protected
return num / 10
> else <
return 0
>
>

Наконец, реализуем TakeDamage для героев. Для простоты будем считать, что вокруг магов всегда есть волшебный щит, возвращающий атакующему 10% от нанесенного урона. Воины лишены каких-либо особых эффектов. Для баланса в будущих версиях игры, вероятно, им стоит добавить броню, благодаря которой воины принимают только часть урона.

Покрываем код тестами

Ну что же, думаю, логика получилась достаточно сложной, чтобы ее захотелось покрыть тестами. В предположении, что приведенный выше код находился в файле heroes.go , создадим файл с тестами под именем heroes_test.go .

Начнем со совсем простого теста:

import (
«github.com/stretchr/testify/require»
«testing»
)

func TestHeroIsDead ( t * testing . T ) <
// t.Skip()
t . Parallel ()
h := heroMage ()
require . False ( t , h . IsDead ())

h . HP = 0
require . True ( t , h . IsDead ())
>

Как видите, ассерты было решено взять из пакета testify/require . Помимо проверок на True и False , также можно проверять Equal , NotEqual , Nil , NotNil , Zero , и много чего еще. Полную документацию вы найдете на godoc.org.

Строчка t.Parallel() говорит, что тест можно запускать параллельно с другими параллельными тестами. Если тест хочется на время выключить, можно написать t.Skip() . Главное не вмержить скипнутые тесты в мастер. Как по мне, уж лучше совсем выкинуть тест, чем держать куски мертвого кода.

Поскольку сейчас мы пишем модульные тесты, настоящим должен быть только код тестируемых функций, а все остальное необходимо замокать. Чтобы не писать моки руками, было решено воспользоваться minimock. Пользоваться им очень просто. Допустим, нам нужен мок с интерфейсом CanTakeDamage . Исправим код таким образом:

// на самом деле это одна строка, но здесь она не влезла:

//go:generate minimock -i github.com/afiskon/golang-unit-testing/⏎
// heroes.CanTakeDamage -o . -s _mock.go
type CanTakeDamage interface <
TakeDamage ( num int ) int
>

# если minimock будет валиться, убедитесь, что у вас объявлен GOPATH:
# export GOPATH=/home/eax/go
go generate ./.

Будем получен файл can_take_damage_mock.go с нашим моком. Пример его использования:

// Mage attack with -10 HP effect
func TestMageAttack ( t * testing . T ) <
t . Parallel ()
h := heroMage ()
m := NewCanTakeDamageMock ( t )
m . TakeDamageMock . ExpectOnce ( 20 ) . Return ( 10 )
h . Attack ( m )
require . Equal ( t , h . HP , 90 )
require . Equal ( t , h . info . ( * MageInfo ) . Mana , 95 )
>

В результате тест проверит, что TakeDamage вызывается ровно один раз и именно с заданным аргументом. Если не сказать ExpectOnce , а метод, тем не менее, будет вызван, то тест не пройдет. Можно также указать конкретную реализацию интересующего метода:

m . TakeDamageMock . Set ( func ( p int ) int < return p >)

Или, например, сказать Expect вместо ExpectOnce и узнать, сколько раз вызывался метод, посмотрев на TakeDamageCounter . Тут проще всего ориентироваться по автокомплиту в IDE. Я лично в настоящее время пользуюсь GoLand от компании JetBrains.

Вообще, IDE очень сильно упрощает работу с тестами. Можно быстро переходить к упавшему тесту, запускать только заданный тест, в том числе под отладчиком, и так далее.

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

Из консоли тесты запускаются как-то так:

go test . / . -test.v

Go умеет по-умному кэшировать результаты выполнения тестов. Иногда это кэширование хочется выключить. Сделать это можно так:

GOCACHE =off go test . / . -test.v

Чтобы оценить степень покрытия кода тестами, говорим:

go test -coverprofile =coverage.out.tmp . / .

Исключаем из отчета сгенерированный код:

cat coverage.out.tmp | grep -v _mock.go > coverage.out

Посмотрим статистику по функциям:

go tool cover -func =coverage.out

. /heroes.go:40: IsDead 100.0%
. /heroes.go:45: IsWarrior 100.0%
. /heroes.go:55: IsMage 100.0%
. /heroes.go:65: Attack 80.0%
. /heroes.go:75: doMageAttack 100.0%
. /heroes.go:91: doWarriorAttack 87.5%
. /heroes.go:108: TakeDamage 83.3%
total: (statements) 90.9%

Посмотрим, какие строки кода остались непокрыты тестами:

go tool cover -html =coverage.out

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

Покрытие кода тестами в языке Go

На отрицательный HP, пожалуй, тест можно и добавить. А вот из-за всевозможных недостижимых кусков кода я бы не стал переживать. На практике покрытие 90% строк кода — очень даже хороший результат.

Два слова об интеграционных тестах

В реальной жизни интеграционными тестами обычно называют те, которые явно не модульные (когда все что можно мокается, как было описано выше), и явно не системные (когда тестируется вся система целиком, честно установленная и настроенная на стенде), а где-то посередке. В принципе, это даже может быть и полноценный модульный тест, который просто очень долго выполняется. Таким тестам хочется присвоить какую-то метку и запускать их отдельно от всех остальных тестов.

Для достижения этого эффекта в Go нужно сделать две вещи. Во-первых, такие тесты нужно сложить в отдельном каталоге. Для определенности, пусть это будет каталог integration . Во-вторых, тесты в этом каталоге должны начинаться со строчки, содержащей метку:

// +build integration

Легко убедиться, что такие тесты не будут выполняться со всеми остальными.

Для их запуска следует воспользоваться командой:

go test . / integration / . -test.v -tags integration

Само собой разумеется, тэгов можно использовать много, и называться они могут как угодно.

Примечание: Если вы пишите код в GoLand, по умолчанию он не индексирует файлы под тэгами. Исправить это можно, введя используемые вами тэги через пробел в GoLand → Preferences… → Go → Build Tags & Vendoring → Custom tags.

Заключение

Как видите, при написании тестов в языке Go есть кое-какие нюансы.

Кое-что, конечно, в итоге осталось за кадром. Например, бенчмарки в Go пишутся почти аналогично тестам. Но о бенчмарках и профайлинге мы лучше поговорим в другой раз.

Полную версию исходников к посту вы найдете в этом репозитории на GitHub.

А чем вы пользуетесь при написании модульных тестов?

Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *