В мире программирования на Go тестирование играет важную роль, обеспечивая качество и надежность кода. Однако многие разработчики, особенно начинающие, часто прибегают к библиотеке testify, а именно её модулю assert, из-за пользы и удобства, которые она предлагает при написании проверок в тестах. Несмотря на это, excessive использование testify/assert может привести к перегруженности зависимостей и излишней сложности тестового кода. Вместо этого можно писать выразительные и лаконичные тесты, самостоятельно реализовав несколько ключевых функций assert, которые покрывают большинство повседневных сценариев тестирования. Такой подход позволяет минимизировать зависимость от сторонних пакетов, сохранить чистоту и простоту тестов.
Одним из наиболее часто встречающихся видов проверок в тестах является утверждение равенства значений. Например, проверка, что возвращаемое из функции значение соответствует ожидаемому. В базовом виде это выглядит как if-условие, в котором при несоответствии вызывается t.Errorf для вывода ошибки с подробностями. Однако этот способ быстро превращается в громоздкий и многословный код, усложняющий чтение и понимание.
Вместо этого можно написать универсальную функцию AssertEqual с использованием обобщений Go, которая будет сравнивать значения любого типа и выводить понятное сообщение об ошибке в случае несоответствия. Чтобы корректно обрабатывать разные типы данных, включая указатели, интерфейсы и сложные структуры, стоит дописать вспомогательную функцию isNil, которая корректно определит, является ли переданное значение nil, вне зависимости от его типа. Так функции сравнения удаётся избежать ошибок, вызванных неверным пониманием nil для различных типов в Go. Дополнительно стоит учитывать, что для некоторых типов (например, time.Time или net.
IP) стандартное сравнение через reflect.DeepEqual будет неэффективным или некорректным. Многие из таких типов имеют собственный метод Equal, который лучше всего использовать для сравнения. Благодаря введению интерфейса equaler и проверке реализует ли тип такой метод, можно задействовать этот внутренний механизм сравнения. Особое внимание стоит уделить оптимизации сравнения массивов байт – это частый и критичный с точки зрения производительности случай.
Вместо сравнения через reflect.DeepEqual эффективнее использовать bytes.Equal, что заметно ускорит работу тестов, особенно при работе с большими данными. В итоге функция AssertEqual обеспечивает надёжные и точные проверки равенства в компактном и легко поддерживаемом виде, покрывая около 70% потребностей в тестах. Ещё одним важным блоком в тестировании являются проверки ошибок.
Обработка ошибок является неотъемлемой частью Go, и грамотное тестирование их значений, типов и сообщений значительно повышает качество кода. Для этого достаточно создать одну функцию AssertErr, которая гибко работает с разными сценариями: проверкой на отсутствие ошибки, проверкой совпадения с конкретным значением ошибки через errors.Is, проверкой типа ошибки через errors.As и даже проверкой подстрок в сообщении ошибки. Удобство AssertErr состоит в том, что она умеет принимать переменное количество параметров, что позволяет вызывать её в разных вариантах.
Например, если не передавать ожидаемый параметр, она проверит, чтоошибка есть любая (non-nil), если передать конкретный error – убедится, что возникла именно эта ошибка, при передаче типа выполнит проверку по типу, а при передаче строки — подтвердит, что сообщение ошибки содержит указанную подстроку. Например, очень часто нам нужно проверить, что функция возвращает ошибку определённого типа, например, *fs.PathError или иного. AssertErr благодаря встроенной проверке errors.As позволяет легко реализовать это без дополнительного кода.
Аналогично, проверка вхождения подстроки в сообщение об ошибке упрощает тесты на генерацию ожидаемых текстов и валидацию понятности ошибок. Интересно, что функция AssertErr допускает как фатальное завершение теста (если ошибка была неожиданно получена при ожидании её отсутствия), так и мягкое уведомление о несоответствии. Такой баланс позволяет ускорить процесс отладки, одновременно сохраняя стабильность и осмысленность тестов. Кроме проверок равенства и ошибок в тестах нередко встречаются более абстрактные или условные проверки. Например, проверка, что длина среза соответствует критерию, или что строка содержит определённый текст.
Делая подобные проверки через AssertEqual с булевыми результатами, код получается корявым и менее читабельным. Для этих целей рационально создать простую функцию AssertTrue, которая утверждает истинность булевого выражения и выводит сообщение в случае провала. Это улучшает читаемость и делает тесты более выразительными. Набор из трёх функций AssertEqual, AssertErr и AssertTrue охватывает подавляющее большинство потребностей в написании тестов, позволяя отказаться от громоздких и множества функций из testify/assert. Такой минимализм выгоден для проекта с точки зрения размера зависимостей, скорости сборки и понимания тестов новичками.
Кроме того, стоит отметить, что для написания тестов с этими функциями не требуется установка дополнительных пакетов и внешних библиотек, что особенно актуально для проектов с жесткими требованиями к безопасности и поддержке. Все функции легко адаптируются под свои нужды, быстро отлаживаются и расширяются при необходимости. В результате, придерживаясь этого подхода, разработчики получают чистый и поддерживаемый тестовый код, который легко читать и развивать. При этом сохраняется вся необходимая функциональность, которую обычно предоставляют более тяжелые сторонние библиотеки. Примеры использования кода подтверждают его эффективность как на обычных типах, так и при сравнении сложных структур, включая временные метки, IP-адреса, байтовые срезы и типизированные ошибки.