Хорошие практики написания кода для автотестов
Что проверяют
Знаете ли вы, как тесты устроены именно в Go: стандартный пакет testing, табличные случаи, подтесты, детектор гонок. И относитесь ли вы к тестам как к обычному коду, который придётся поддерживать.
Как отвечать
Перечислите практики, привязанные к языку, а не общие слова про читаемость:
- табличные тесты с подтестами, имя случая видно в отчёте о падении;
- никакого общего изменяемого состояния между тестами, уборка через
t.Cleanup; t.Helper()во вспомогательных функциях, иначе падение указывает на строку хелпера, а не теста;- зависимости за интерфейсами и простые ручные фейки, генерация моков только там, где фейк выходит громоздким;
- HTTP проверяется через
net/http/httptest, время подменяется своими часами; - прогон с
-raceв CI, иначе гонки находит прод; - интеграционные тесты отделены тегом сборки и поднимают зависимости в контейнерах.
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := Parse(tc.in)
if !errors.Is(err, tc.wantErr) {
t.Fatalf("ошибка %v, ожидалась %v", err, tc.wantErr)
}
...
})
}Где спотыкаются
Сравнивают ошибки по тексту сообщения. Такой тест ломается от правки формулировки и не ломается там, где сменился смысл ошибки. Нужны объявленные значения ошибок и проверка через errors.Is или errors.As.
Включают t.Parallel() в подтестах и получают случайные результаты. Причины две: общее состояние в пакете и захват переменной цикла, который в старых версиях языка приводил к тому, что все подтесты видели последний случай.
Ждут асинхронный результат через time.Sleep. На своей машине проходит, в CI под нагрузкой падает, а лечится это увеличением паузы, пока прогон не начинает занимать минуты. Синхронизировать нужно каналом или опросом с таймаутом.