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

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 8 из 25 ~18 мин +20 XP

True, False, Nil, NotNil и Empty: простые проверки

Половина проверок в боевом тесте — простые: флаг выставлен, ошибка не вернулась, слайс не пуст, поле обнулилось. У testify на каждый такой случай есть одноимённое утверждение, и замена ручной развилки на assert.True или assert.Nil делает отчёт о провале читаемым сразу, без чтения кода теста. Разберём базовый набор, его границы и ловушки, на которых собирают баги.

Содержание урока · 11 разделов

После первых уроков курса в руках уже есть тяжёлая артиллерия: Equal для сравнения значений, ElementsMatch для коллекций, семейство проверок ошибок. Но повседневный тест — это не только сравнения. Проверить, что переключатель включился, что метод вернул nil вместо объекта, что очередь опустела, что счётчик обнулился — такие проверки занимают больше половины строк в тестах, которые вы будете писать каждую неделю. Testify собирает их в короткие одноимённые утверждения: True, False, Nil, NotNil, Empty, NotEmpty, Zero, NotZero, Len. Каждое работает одинаково: провал помечает тест через t.Errorf, в отчёт уходит и полученное значение, и ожидаемое, а вместе с ними — ваше сообщение, если вы его передали. В этом уроке пройдёмся по набору целиком, а затем остановимся на двух ловушках, на которых регулярно собираются редкие и жёсткие баги: nil, спрятанный в интерфейсе, и разница между пустым и нулевым значением коллекции.

True и False: флаги без развилок#

assert.True(t, ok) требует, чтобы ok был равен true, assert.False(t, ok) — чтобы false. На первый взгляд это дубль проверки ok == true через if, но разница проявляется при провале: if молча не выполнит ветку, а тест-функция с assert.True напишет в отчёт «Should be true» и покажет фактическое значение флага. Именно поэтому в тестах развилки if почти не живут: они проверяют вручную и молча, а тест обязан шуметь. Полезная деталь API: и True, и False принимают те же необязательные msgAndArgs, что и остальные утверждения — сообщение с контекстом («токен истёк за секунду до операции») попадёт в отчёт рядом с провалом. Частая ошибка новичка — assert.Equal(t, true, ok) вместо assert.True(t, ok): работает так же, но читается хуже и выводит более шумный diff. Инвертированные версии у каждой проверки тоже есть на месте: NotNil и NotEmpty — прямые антиподы Nil и Empty, и в паре с True/False они покрывают большинство сценариев с булевыми полями и результатами валидации.

Проверка флагов валидации

GO
func TestValidation(t *testing.T) {
        user := User{Name: "Анна", Age: 30}

        assert.True(t, user.IsValid(), "заполненный пользователь должен проходить валидацию")

        user.Age = -1
        assert.False(t, user.IsValid(), "отрицательный возраст не должен проходить")
}

Каждая проверка снабжена сообщением с человеческим объяснением: при провале отчёт покажет не только «expected false, got true», но и причину, ради которой проверка ставилась. Сообщения — дешёвая привычка, которая экономит часы при чтении отчётов CI.

assert.Nil: границы nil в Go#

assert.Nil(t, v) проверяет, что v — нулевое значение своего типа: nil для указателей, слайсов, карт, каналов, интерфейсов и функций, ноль для чисел, пустая строка для string, false для bool. Это шире, чем проверка v == nil: для числа 0 и строки "" Nil тоже сработает, потому что в терминах testify «нулевой» и «пустой» для примитивов означают одно и то же. Не путайте с NotNil: assert.NotNil(t, err) иногда встречается в чужих тестах вместо assert.Error, но так делать не стоит — для ошибок есть специальные проверки с более ясным выводом, они разобраны в уроке про проверку ошибок. Ещё одна привычка, которая отличает зрелый тест: проверять Nil не только на желаемые результаты, но и на то, что освобождение ресурсов обнулило указатель — так ловят использования после закрытия.

Empty и NotEmpty: что считается пустым#

assert.Empty(t, v) отвечает на вопрос «здесь ничего полезного?» для самых разных типов: пустая строка, слайс длины ноль, пустая карта, nil того же семейства, нулевой канал, ноль числа, false. Утверждение построено на helper-функции, которая сначала пробует взять длину через отражение, а для примитивов сравнивает с нулевым значением. Отсюда практическое следствие: Empty объединяет в одной проверке то, что в чистом Go требует разных выражений — len(s) == 0, s == nil, s == "". NotEmpty — зеркальная проверка, и она особенно удобна для входных данных: assert.NotEmpty(t, resp.Items, "API обязан вернуть хотя бы один элемент") читается как требование продукта, а не как код.

Empty на четырёх типах

GO
func TestEmpty(t *testing.T) {
        var nilSlice []int
        emptySlice := []int{}
        filled := []int{1}

        assert.Empty(t, nilSlice)      // nil-слайс пуст
        assert.Empty(t, emptySlice)    // пустой слайс пуст
        assert.Empty(t, "")            // пустая строка пуста
        assert.Empty(t, map[int]int{}) // пустая карта пуста
        assert.NotEmpty(t, filled)
}

И nil-слайс, и слайс с нулевой длиной проходят Empty — для testify оба означают «ничего нет». Если ваш тест обязан различать эти состояния, используйте Len из урока про коллекции или явный Equal с nil.

Что Empty считает пустым#

ТипПустое значениеКомментарий
string""и nil-семантики тут нет — только длина
слайсnil или длина 0оба состояния эквивалентны
картаnil или длина 0как и слайс
каналnilзакрытый пустой канал тоже пройдёт Empty
число00, 0.0 — всё пустое
boolfalsefalse эквивалентен пустому
указательnilа ненулевой указатель на пустую структуру — NotEmpty

Zero и NotZero: нулевые значения#

Zero — родственник Empty, но с более строгой семантикой: он сравнивает значение с нулевым значением ТИПА, а не с абстрактной пустотой. assert.Zero(t, counter) после сброса счётчика читается точнее, чем Empty, потому что говорит о типовой нулевости. Для структур Zero проверяет рефлексией каждое поле — и здесь прячется нюанс: структура со всеми нулевыми полями пройдёт Zero, но структура с любым ненулевым полем провалит проверку с подробным diff. На практике Zero чаще всего встречается на числах и time.Duration, NotZero — на проверках, что что-то посчиталось: assert.NotZero(t, resp.Elapsed, "замер времени обязан быть ненулевым"). Для float-полей помните урок про сравнение чисел с плавающей точкой: Zero на аккумулированных долях лучше ставить только там, где ноль действительно точен.

Какое утверждение выбрать#

  • Флаг или результат валидации — True и False, с сообщением о смысле проверки.
  • Возможное отсутствие объекта — Nil и NotNil; для ошибок — Error и NoError, а не Nil.
  • «Здесь нет данных» — Empty и NotEmpty: покрывают строки, слайсы, карты одним словом.
  • Типовая нулевость после сброса — Zero и NotZero, особенно для чисел и Duration.
  • Точная длина коллекции — Len: она различает nil и пустую коллекцию по контексту проверки.

Чтение провала: что покажет отчёт#

Провал простой проверки информативен ровно настолько, насколько вы о нём позаботились. На Nil testify напишет ожидание nil и фактическое значение — если там спрятана структура, вывод будет многострочным. На Empty покажет тип и содержимое пустоты: строку, длину коллекции, ноль. На Zero выведет diff между нулевым значением типа и фактическим. Всё это можно и нужно снабжать сообщением: без него отчёт говорит «что», без «зачем», и через месяц после написания теста вы будете гадать, чем именно был важен этот Nil. Отдельный приём — проверять в сообщении условия, при которых проверка справедлива: «очередь пуста после Drain, но не до первого Run» — такое сообщение превращает провал в подсказку, а не в загадку.

Сообщение как документация проверки

GO
assert.Nil(t, rows, "после Drain очередь обязана опустеть")

assert.NotEmpty(t, report.Rows,
        "отчёт без строк после обработки — сигнал пропущенного шага")

Оба сообщения объясняют требование, а не повторяют проверку. Первый читатель отчёта — вы сами через полгода: сообщение экономит погружение в контекст.

Проверка: Nil и интерфейсы

В тесте объявлено: var e error = (*MyError)(nil). Что вернёт assert.Nil(t, e)?

Проверка: Empty на nil и пустом

Какие проверки пройдут для var s []int (nil-слайс)?

Что выведет отчёт при провале?

Прочитайте код и выберите, что он выведет:

var users []*User // nil-слайс

assert.NotEmpty(t, users, "ожидаем пользователей")
assert.Zero(t, 0)
assert.Zero(t, "0")

Соберите набор базовых проверок

Допишите утверждения: истинность флага, отсутствие объекта после освобождения, пустота карты, ненулевой замер времени.

assert.(t, session.IsActive, "сессия должна быть активной") release(session) assert.(t, session.conn, "соединение обязано обнулиться") assert.(t, cache, "кэш обязан опустеть после сброса") assert.(t, report.Elapsed, "время обработки не может быть нулевым")

Соберите тест жизненного цикла сессии

Расставьте строки теста: создание, проверка активности, освобождение, две проверки после.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: базовые проверки

Термин

assert.True / assert.False

нажмите, чтобы перевернуть
Определение

Проверка булевых значений. Провал выводит фактическое значение и ваше сообщение — в тестах эти проверки заменяют молчаливые if.

1 / 6

Что дальше#

Базовый набор собран: флаги, nil, пустота, нулевые значения — теперь можно читать чужие тесты без словаря и писать свои так, чтобы отчёт рассказывал историю сам. Следующий шаг — сообщения: у каждого утверждения testify есть необязательный хвост msgAndArgs, и умение писать его коротко и по делу отличает тест, который приятно чинить, от теста, который просто краснеет. После сообщений перейдём к сборным конструкциям — сьютам с методами подготовки, где базовые проверки будут жить уже в организованных группах. Напомню про среду: все проверки выполняются внутри go test, поэтому полный прогон — локальный Go; в браузерной песочнице вы можете потренировать заполнение пропусков и порядок строк, а запуск тестов — уже на своей машине. Общий маршрут — на странице курса по testify, а если хочется освежить, откуда берутся нулевые значения в Go, — это урок про переменные и типы базового курса.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • True, False, Nil, NotNil, Empty, NotEmpty, Zero, NotZero — ежедневный набор проверок testify; каждая принимает msgAndArgs.
  • Nil проверяет нулевое значение типа: не только nil, но и 0, "" и false — это шире, чем v == nil.
  • Интерфейс с nil-указателем конкретного типа не равен nil — ловушка Go, сигнал чинить возвращаемый тип, а не тест.
  • Empty объединяет «пустоту» разных типов: nil и длина 0 для слайсов и карт, пустая строка, ноль, false.
  • Zero сравнивает с нулевым значением типа: строка "0" не нулевая — провалит проверку.
  • Разницу «nil против пустого» на уровне контракта кодируйте Len или Equal — JSON null и [] не одно и то же.
  • Для ошибок используйте Error и NoError: Nil на err работает, но вывод менее ясный.

Домашнее задание

Возьмите структуру с булевым полем и слайсом и напишите тест жизненного цикла: creation — True и NotEmpty, после Reset — Nil на указателе, Empty на слайсе, Zero на счётчике. К каждой проверке добавьте сообщение с объяснением смысла. Затем один раз намеренно перепутайте Empty и Zero на строковом поле и посмотрите, как отличает их вывод при провале.

Личные заметкиПусто
Хранятся только в вашем браузере (Testify · True, False, Nil, NotNil и Empty: простые проверки)

Вопросы и ответы по уроку

Чем assert.Empty отличается от assert.Nil?

Nil — про нулевое значение конкретного значения: nil, 0, пустая строка, false. Empty — про отсутствие полезного содержимого: он проходит и для nil, и для коллекций нулевой длины. Практическое правило: Nil — для объектов и указателей, Empty — для данных внутри объектов и коллекций.

Почему assert.Nil провалился на переменной, в которой явно лежит nil?

Скорее всего, это интерфейсная ловушка Go: в переменную интерфейсного типа записан nil-указатель конкретного типа, и пара «тип-значение» внутри интерфейса не пустая. Чинить нужно код: возвращать ненулевой тип только вместе с ненулевым значением, а в тесте — не ослаблять проверку до assert.True(t, v == nil).

Стоит ли проверять err через assert.Nil?

Работать будет, но специальных проверок ошибок лучше: assert.NoError даёт более ясный вывод, а EqualError и ErrorIs позволяют сверить текст или саму ошибку. Nil на err оставьте там, где ошибки нет как концепции — например, в проверках обнуления ресурсов.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Viper25 уроков~9 чпрактика25 уроков уже доступны

Viper: конфигурация приложений на Go

Интерактивный курс Viper для Go: YAML и env-переменные, приоритеты источников, Unmarshal, WatchConfig, профили окружений и секреты. 25 уроков с примерами.

  • Собирать конфиг из файлов, окружения и флагов в один реестр
  • Читать значения типизированно: Get, GetString, Unmarshal в структуры
  • Задавать приоритеты источников: флаг > env > файл > default
  • Перечитывать конфиг на лету через WatchConfig и OnConfigChange