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

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

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

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

Первый тест: assert.Equal и go test

Первый тест go с assert.Equal заканчивается не зелёным ok, а умением читать провал: testify печатает expected и actual с diff, и по этому отчёту баг находится за секунды. Соберём тест с нуля: сигнатура TestXxx, запуск go test -v, нарочито сломанная проверка и разбор отчёта построчно.

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

Библиотека установлена в прошлом уроке и пока не сделала ничего: сам по себе импорт в тест-файле не запускает ни одной проверки. Соберём теперь настоящий тест целиком — от пустого файла до отчёта о провале — и пойдём не по гладкой дорожке: второй прогон будет красным, и в этом главный план урока. Провал testify-теста — не просто строка FAIL: отчёт показывает ожидаемое значение, фактическое, diff между ними и адрес строки, где проверка не сошлась. Кто умеет читать этот отчёт, тот чинит регрессии заметно быстрее того, кто перечитывает логи. По пути разберём анатомию тестовой функции, флаг -v, статусы go test и самую частую ошибку новичка — перепутанные местами expected и actual. Полный прогон, напомню, требует локального Go: браузерная песочница не исполняет пакеты тестирования, поэтому запускаем всё в терминале своей машины.

Анатомия первого теста: TestXxx(t *testing.T)#

Тест в Go — обычная функция с тремя условиями. Первое: файл с суффиксом _test.go, иначе go test его не соберёт — суффикс, не префикс, и это частая опечатка у пришедших из других языков. Второе: имя начинается с Test и продолжается заглавной буквой — TestParseUser, TestCalcSum; компилятор равнодушен к testparseuser, но runner такой функции не найдёт. Третье: единственный аргумент t testing.T — через него testify и весь reporting общаются с пакетом testing: все assert-функции принимают t первым аргументом и отмечают провалы именно на нём. Забытый t не скомпилируется, лишний параметр — тоже. Пакет у теста тот же, что у тестируемого кода: Split лежит в calc.go пакета calc, тест — в calc_test.go того же пакета, и непубличные идентификаторы видны напрямую. Альтернативный пакет calc_test с внешним API пригодится позже, в теме заглушек. Сигнатура фиксирована: func TestXxx(t testing.T), ничего не возвращает — на этом магия заканчивается, дальше обычный код.

Тест, который должен пройти

GO
// calc_test.go
package calc

import (
        "testing"

        "github.com/stretchr/testify/assert"
)

// Split делит строку по разделителю и возвращает число частей
func TestSplit(t *testing.T) {
        got := Split("a,b,c", ",")

        assert.Equal(t, 3, got)
}

Файл в пакете calc, функция с Test и t *testing.T, внутри один assert.Equal: ожидаемое 3 вторым аргументом, фактическое got третьим. Комментарий над Split — не украшение: тест должен читаться отдельно от кода. В реальном проекте Split живёт в calc.go, а тест — в calc_test.go рядом; локальный прогон go test ./... покажет зелёное ok calc.

Запуск: go test и флаг -v#

Команда go test запускается в каталоге пакета, а go test ./... — по всем пакетам модуля разом. По умолчанию вывод скромен до аскетизма: ok calc или FAIL calc — и всё, имена прошедших тестов не печатаются. Флаг -v (verbose) разворачивает историю: каждый тест получает строку === RUN с именем и завершение PASS или FAIL с временем, и по журналу видно, какие проверки вообще выполнялись. Второй полезный флаг — -run: go test -run TestSplit гоняет только совпавшие по имени тесты, и на большом пакете экономит минуты. Третья деталь, которая сбивает с толку: go test кэширует результаты — неизменившийся пакет отвечает ok с пометкой (cached) и по-настоящему не перезапускается; свежий прогон задаётся флагом -count=1. Как и весь курс, полный прогон требует локального Go — браузерная песочница не исполняет пакеты тестирования, поэтому здесь терминал заменяют интерактивы: квизы, сборка отчёта по строкам, чтение вывода по частям.

Нарочный провал и его отчёт

GO
func TestSplit(t *testing.T) {
        got := Split("a,b", ",")

        assert.Equal(t, 3, got)
}

// go test -v
// === RUN   TestSplit
//     calc_test.go:11:
//         Error Trace:	calc_test.go:11
//         Error:       Not equal:
//                      expected: 3
//                      actual  : 2
//                      Test:        TestSplit
// --- FAIL: TestSplit (0.00s)
// FAIL
// FAIL    calc    0.002s

Разница с рабочим тестом — одна буква: Split получает a,b вместо a,b,c и возвращает 2. Тест падает, и testify печатает отчёт: адрес строки, Not equal, ожидаемое 3, фактическое 2, имя теста. Падение локализовано: --- FAIL относится к TestSplit, а не ко всему пакету — остальные тесты файла выполняются как ни в чём не бывало.

Читаем отчёт о провале#

Отчёт читается сверху вниз по четырём слоям. Слой запуска: === RUN TestSplit — тест стартовал. Слой деталей: строка с адресом файла и строки проверки, затем Error: Not equal и два значения — expected, то, что вы передали вторым аргументом, и actual, то, что пришло из кода; testify печатает ещё строку Test с именем — удобно, когда журнал длинный. Слой вердикта: --- FAIL: TestSplit (0.00s) с временем. Слой итога: FAIL пакета и суммарное время; команда возвращает код выхода 1 — CI краснеет именно из-за него, а не из-за текста. Заметьте тонкость: testify пишет слова «ожидаемое» и «фактическое», но не знает, кто из них прав в вашей предметной области, — библиотека лишь честно показывает, что вы передали. Кто есть кто, решаете вы, и здесь критичен порядок аргументов: о классической ошибке с перепутанными значениями — ниже.

Что происходит при нескольких провалах в одном тесте. Проверки assert не прерывают функцию: первый упавший Equal лишь вносит строку в отчёт, и выполнение идёт дальше. Следствие — за один прогон видны все расхождения сразу: если из пяти проверок не сошлись три, отчёт покажет три блока expected/actual подряд, а не первый. Это экономит цикл «исправил — перезапустил — нашёл следующее», который на пакете из сотен тестов ощутимо дорог. Оборотная сторона: вторая проверка выполняется на данных, скомпрометированных первым провалом. Если результат парсинга оказался nil, следующие проверки не добавят информацию, а упадут с паникой и завалят отчёт стеком горутины — поэтому подготовку данных ведут через require, и это тема следующего урока. Паника в тесте тоже даёт FAIL, но выглядит иначе: вертикальный стек вызовов вместо аккуратного expected/actual. Увидеть такой отчёт хотя бы раз стоит — чтобы потом отличать мгновенно.

Два провала в одном тесте

GO
func TestUser(t *testing.T) {
        user := ParseUser("ann", 17)

        assert.Equal(t, "ann", user.Name)
        assert.Equal(t, 18, user.Age)
        assert.Equal(t, "ann@example.com", user.Email)
}

Возраст 17 против ожидаемого 18 и пустой Email против ann@example.com — два провала; имя сойдётся. Отчёт покажет два блока Error по очереди, с адресами строк. Если ParseUser на плохом входе возвращает nil, обращение к полю упадёт с паникой — тот случай, когда нужна ранняя остановка require, и мы её разберём в третьем уроке.

Мини-ритуал первого теста#

  • Файл с суффиксом _test.go в пакете тестируемого кода
  • Функция TestXxx с единственным аргументом t *testing.T
  • assert.Equal(t, эталон, результат) — ожидаемое вторым, фактическое третьим
  • Подготовка данных — через require, чтобы тест останавливался на битом входе
  • Запуск go test -v в каталоге пакета, go test ./... — по всему модулю
  • Красный тест — не авария, а данные: читайте expected, actual и Error Trace

Статусы и строки go test#

Что видитеЧто это значитЧто делать
ok calcВсе тесты пакета прошлиНичего; с -v покажет имена и PASS по каждому
--- FAIL: TestSplitУпал конкретный тестЧитать блок Error: expected, actual и адрес строки
FAIL calcВ пакете есть провалы или паникаИскать первый --- FAIL выше по выводу
[no test files]go test не нашёл файлов _test.goПроверить суффикс файла и каталог
ok calc 0.002s (cached)Кэш: код не менялся, прогон честный, но старыйСвежий запуск — go test -count=1

Проверка: порядок аргументов

Как правильно вызвать проверку равенства в testify?

Проверка: флаг -v

Что добавляет go test -v по сравнению с простым go test?

Что напечатает прогон?

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

// Abs возвращает модуль числа
func TestAbs(t *testing.T) {
        got := Abs(-7)

        assert.Equal(t, 7, got)
}

Допишите проверку

Восстановите тип t, имя пакета проверки и поле структуры.

func TestParseUser(t *testing.) { got := ParseUser("ann", 18) assert.(t, "ann", got.Name) assert.Equal(t, 18, got.) }

Соберите отчёт о провале

Расставьте строки вывода go test -v по порядку — от запуска теста до итога пакета (вывод сокращён).

Строки

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

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

Карточки: первый тест

Термин

assert.Equal(t, want, got)

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

Проверка равенства: *testing.T первым аргументом, эталон вторым, результат кода третьим. Провал печатает expected, actual и адрес строки.

1 / 6

Спорное: один assert на тест#

В TDD-мире живёт правило «один assert на тест»: тест должен падать по одной причине, и имя честно описывает провал. На практике testify оно ломается о первый же тест объекта: пять полей — пять вызовов Equal, и пять тестов с одинаковым парсингом ради каждого поля выглядят натужно. Моя позиция: несколько assert-ов в одном тесте стоит избегать не механически, а по границе — пока проверки относятся к одному сценарию одного объекта, они имеют право жить рядом; как только второй assert проверяет другой сценарий, тест делится. Компромисс, который работает на ревью: пачка Equal по полям одного результата — нормально, а assert на «и парсинг, и запись в базу, и ответ» — нет. Строгая школа права в одном: провалы внутри одного теста связаны, и require-версии с ранней остановкой дисциплинируют тут лучше любых правил.

Что дальше#

Первый тест собран, сломан и прочитан — дальше встаёт вопрос о судьбе провала: когда тест должен падать сразу, а когда полезно увидеть все расхождения. Это граница assert и require, и она размечена в следующем уроке курса. После require логичный шаг — проверки ошибок: Error, NoError и EqualError вместо if err != nil, тема четвёртого урока. Примеры урока прогоняются локальным Go: соберите тест из блока выше, сломайте значение нарочно и прочитайте свой первый diff — рука запоминает этот отчёт быстрее, чем глаза. Оглавление и маршрут курса, если хотите опережать расписание, — на странице курса testify.

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

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

Вывод

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

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

  • Тест — функция TestXxx(t *testing.T) в файле с суффиксом _test.go: без заглавной буквы после Test и без t runner тест не увидит.
  • assert.Equal(t, want, got): t первым, эталон вторым, результат кода третьим — при провале testify печатает expected, actual и адрес строки.
  • go test -v показывает === RUN и PASS/FAIL по каждому тесту; -run сужает прогон, -count=1 обходит кэш результатов.
  • Отчёт провала читается слоями: RUN, блок Error с адресом строки и значениями, --- FAIL с именем, FAIL пакета и код выхода 1.
  • Перепутанные expected и actual не ломают тест, но переворачивают diff: эталон — вторым аргументом, результат — третьим.
  • Несколько assert в одном тесте падают вместе: за прогон видны все расхождения; битая подготовка данных — сигнал для require.
  • Красный тест — данные: assert с гипотезой и его отчёт часто заменяют принт-отладку.

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

Напишите тест к любой своей функции с assert.Equal и запустите go test -v — убедитесь в PASS. Затем нарочно поменяйте местами эталон и результат, перезапустите и сравните два отчёта: запомните, как выглядит перевёрнутый diff, чтобы узнавать его в чужих провалах.

Личные заметкиПусто
Хранятся только в вашем браузере (Testify · Первый тест: assert.Equal и go test)

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

Почему тест с ошибкой в имени не запускается, а компиляция проходит?

go test ищет функции по правилу: имя начинается с Test и продолжается заглавной буквой, аргумент t *testing.T. Всё остальное — обычные функции пакета. Компилятору такие функции корректны, поэтому ошибка видна только в выводе: «no tests to run» и отсутствие строки === RUN.

Что делать, если go test показывает ok (cached), а я менял код?

Кэш сбрасывается изменением файла или переменных окружения; если результат всё равно старый, запустите go test -count=1 — он игнорирует кэш и выполняет тесты заново. Флаг -v к кэшу отношения не имеет: он управляет подробностью вывода, а не свежестью прогона.

Можно ли запускать эти тесты в браузерной песочнице сайта?

Полный прогон go test — нет: песочница исполняет чистый Go без пакетов тестирования и модулей. Интерактивы урока — квизы, predict, fill и order — закрывают чтение кода и отчётов, а запуск и красный diff вы получаете локальным Go за пару минут.

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

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

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

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

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

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

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