Как написать юнит-тест для функции, которая читает данные из базы и считает среднее?
Что проверяют
Умеете ли вы писать тестируемый код, а не только тесты. Главная мысль, которую ждут: подсчёт среднего и чтение из базы надо разделить. Следом спрашивают, чем юнит-тест отличается от интеграционного и как проверить сам SQL.
Как отвечать
Разбейте функцию на части:
- чистая логика:
func Average(values []float64) (float64, error). Тестируется таблицей случаев сt.Run, без всякой базы; - доступ к данным: интерфейс на стороне потребителя, например
type ValuesReader interface { Values(ctx context.Context) ([]float64, error) }. Сервис принимает интерфейс, в тесте ему отдают фейк, который возвращает нужный срез или ошибку; - слой SQL: если нужно проверить сам запрос и сканирование строк, берут
go-sqlmock, он подставляет*sql.DBи сверяет ожидания черезExpectationsWereMet. Настоящий Postgres поднимают через testcontainers-go, но это уже интеграционный тест, его отделяют build-тегом илиtesting.Short().
Какие случаи покрыть: пустой набор (решите заранее, ошибка это вроде ErrNoData или ноль; деление float64 на ноль в Go даёт NaN без паники), одно значение, отрицательные числа, ошибка запроса, ошибка сканирования и ошибка из rows.Err() после цикла.
Хороший ход назвать альтернативу: посчитать AVG(value) в самой базе. Тогда на пустой таблице придёт NULL, и сканировать его нужно в sql.NullFloat64.
Где спотыкаются
Не проверяют rows.Err(). Обрыв соединения посреди выборки выглядит как нормальное завершение цикла, и функция спокойно возвращает среднее по половине данных.
Сравнивают дробные числа через ==. Сравнивать надо с допуском, например math.Abs(got-want) < 1e-9.
Не знают, что ExpectQuery в sqlmock по умолчанию трактует строку как регулярное выражение. Запрос со скобками или $1 не совпадёт, пока его не обернут в regexp.QuoteMeta или не включат sqlmock.QueryMatcherEqual.