Вопросы по веб-сервисам и тестам на Go на собеседовании
Веб-часть Go проверяют через стандартную библиотеку: почти всё решается net/http без фреймворков, и это ожидают увидеть в ответе. Тесты спрашивают отдельно, потому что табличный стиль и детектор гонок это часть культуры языка.
Из чего состоит тема
Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.
- net/http сервер8
- testing: основы8
- io, time, stdlib7
- Модули и инструменты7
- HTTP-клиент и таймауты6
- JSON: encoding/json6
- Table-driven тесты6
- Бенчмарки и -race6
- Моки через интерфейсы4
- Роутинг и middleware4
Разборы подтем
Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.
- Тестирование в Go: пакет testing8 вопросов
- Table-driven тесты в Go6 вопросов
- Бенчмарки и детектор гонок в Go6 вопросов
- Моки в Go через интерфейсы4 вопросов
- Модули Go: go.mod и go.sum7 вопросов
- HTTP-сервер на net/http8 вопросов
- Роутинг и middleware в Go4 вопросов
- JSON в Go: encoding/json6 вопросов
- HTTP-клиент в Go6 вопросов
- io.Reader, time и стандартная библиотека Go7 вопросов
Примеры вопросов с разбором
- Как выглядит бенчмарк в пакете testing?A)func BenchmarkXxx(b *testing.B) с циклом for i := 0; i < b.N; i++B)Обычная func Xxx() с ручным замером через time.Now в начале и в самом конце функцииC)Функция теста с аннотацией @Benchmark, поставленной над её объявлением сверхуD)Флаг -time у go run, который печатает общее время выполнения программы в конце
показать ответ и разбор
+A)func BenchmarkXxx(b *testing.B) с циклом for i := 0; i < b.N; i++// разбор: Бенчмарк — func BenchmarkXxx(b *testing.B) в _test.go, где измеряемую операцию гоняют в цикле for i := 0; i < b.N; i++. Число итераций b.N подбирает сам фреймворк, наращивая его, пока замер не станет статистически стабильным, и печатает ns/op (и B/op, allocs/op при -benchmem). Запуск: go test -bench=. Спот-чек: BenchmarkAdd отработал b.N раз.
- Как в Go принято подменять зависимость (например хранилище) в тесте?A)Патчить нужные функции прямо в рантайме специальной monkey-patch библиотекойB)Глобальной переменной-флагом, который прод-код проверяет в тестовом режиме работыC)Через рефлексию подменять поля структуры на лету прямо перед вызовом методаD)Принимать зависимость через интерфейс и передавать в тесте свою реализацию
показать ответ и разбор
+D)Принимать зависимость через интерфейс и передавать в тесте свою реализацию// разбор: Идиома Go — dependency injection через интерфейс: код зависит от мелкого интерфейса (Store с нужными методами), а в тесте передают свою простую реализацию-заглушку (или сгенерированный мок). Никакого monkey-patching и рефлексии: подмена статически типобезопасна и явна. Отсюда снова принцип «маленькие интерфейсы у потребителя» — они и делают код тестируемым.
- Что задаёт файл go.mod и команда go mod init?A)Список тестов, которые прогонит команда go test в этом каталоге проектаB)Путь модуля, версию Go и его прямые зависимости с версиямиC)Конфигурацию линтера и правил форматирования кода для всего проектаD)Скрипты сборки, запуска и прочие команды проекта, ровно как package.json в Node.js
показать ответ и разбор
+B)Путь модуля, версию Go и его прямые зависимости с версиями// разбор: go.mod — манифест модуля: строка module (путь импорта модуля), директива go (версия языка) и require со списком прямых зависимостей и их версий. go mod init <path> создаёт его. Точные версии всего дерева (включая транзитивные) и их хеши фиксируются в go.sum. Это не скрипты сборки и не конфиг линтера.
- Как выглядит идиоматичный table-driven тест в Go?A)Отдельная самостоятельная тест-функция на каждый отдельный набор входных данныхB)Срез структур-кейсов, по нему цикл с t.Run(name, ...) на каждыйC)Внешний JSON-файл с кейсами, который читают рефлексией во время прогона тестаD)Параметризованный класс с провайдером данных, указанным в специальной аннотации
показать ответ и разбор
+B)Срез структур-кейсов, по нему цикл с t.Run(name, ...) на каждый// разбор: Идиома: срез анонимных структур {name, вход, ожидание}, а затем цикл, где каждый кейс запускается как подтест t.Run(c.name, func(t *testing.T){ ... }). Плюсы: добавить случай — одна строка; подтесты именованы (видно, какой упал) и запускаются выборочно go test -run Test/case. Спот-чек: подтесты pos/zero/neg прошли по отдельности.
- Как выглядит тест в стандартном пакете testing и как его запустить?A)func check() с assert из внешней библиотеки, а запускается всё обычным go runB)Метод класса TestCase с методами setUp и tearDown, запуск через внешний раннерC)func TestXxx(t *testing.T) в файле _test.go; запуск go testD)Функция с аннотацией @Test сверху, запускаемая командой go verify
показать ответ и разбор
+C)func TestXxx(t *testing.T) в файле _test.go; запуск go test// разбор: Тест — функция func TestXxx(t *testing.T) в файле, чьё имя оканчивается на _test.go. Провал сообщают через t.Error/t.Errorf (тест продолжается) или t.Fatal (останавливает эту тест-функцию). Запуск — go test (весь пакет) или go test -run. Никаких аннотаций, классов и внешних раннеров: тестовый фреймворк встроен в тулчейн.
- После resp, err := http.Get(url) и проверки ошибки — что обязательно сделать с resp.Body?A)Ничего: тело закрывается сборщиком мусора автоматическиB)Закрыть через defer resp.Body.Close(), иначе течёт соединениеC)Вызвать resp.Body.Flush() перед чтением содержимогоD)Скопировать тело в строку — только тогда соединение освободится
показать ответ и разбор
+B)Закрыть через defer resp.Body.Close(), иначе течёт соединение// разбор: Тело ответа — открытый поток поверх TCP-соединения; его обязательно закрывать: defer resp.Body.Close() сразу после проверки err. Иначе соединение не вернётся в пул и утечёт (в цикле быстро упрёшься в лимит). GC его вовремя не закроет. Для переиспользования keep-alive тело ещё и стоит дочитать до конца (io.Copy в io.Discard) перед Close.
- json.Marshal сериализует структуру. Какие поля попадут в JSON?A)Все поля подряд, включая приватные — рефлексия добирается и до нихB)Только поля с явным тегом json, прочие пропускаютсяC)Поля в алфавитном порядке имён, приватные в концеD)Только экспортируемые поля; имя берётся из тега json или самого поля
показать ответ и разбор
+D)Только экспортируемые поля; имя берётся из тега json или самого поля// разбор: encoding/json через рефлексию видит ТОЛЬКО экспортируемые (с заглавной буквы) поля — неэкспортируемые молча пропускаются (частый баг: поле с маленькой буквы «не сериализуется»). Имя ключа берётся из тега json, иначе — имя поля как есть. omitempty убирает пустые. Спот-чек: age (с маленькой) в вывод не попал.
- Как в net/http идиоматично устроено middleware (логирование, auth)?A)Через глобальные хуки, которые регистрируют в поле http.Server.OnRequestB)Функция, оборачивающая http.Handler в новый Handler с логикой до/послеC)Наследованием от базового http.BaseHandler с переопределением методаD)Отдельным потоком-перехватчиком перед каждым обработчиком
показать ответ и разбор
+B)Функция, оборачивающая http.Handler в новый Handler с логикой до/после// разбор: middleware в Go — функция func(next http.Handler) http.Handler: она возвращает новый Handler, который делает что-то ДО (лог, проверка токена), вызывает next.ServeHTTP и что-то ПОСЛЕ. Их выстраивают в цепочку, оборачивая друг в друга. Никаких глобальных хуков или наследования — только композиция мелкого интерфейса http.Handler.
- Какая сигнатура у обработчика HTTP в net/http?A)func(*http.Request) http.Response — принимает запрос и возвращает готовый ответB)func(w http.ResponseWriter, r *http.Request) — ответ пишем в wC)func(ctx context.Context) ([]byte, error) — возвращаем тело и ошибкуD)func(req, resp map[string]any) — запрос и ответ как словари
показать ответ и разбор
+B)func(w http.ResponseWriter, r *http.Request) — ответ пишем в w// разбор: Обработчик в net/http имеет вид func(w http.ResponseWriter, r *http.Request): читаешь запрос из r, а ответ ПИШЕШЬ в w (заголовки, статус, тело) — функция ничего не возвращает. Такую функцию регистрируют через http.HandleFunc или оборачивают в http.HandlerFunc, которая реализует интерфейс http.Handler с методом ServeHTTP.
это 9 из 62
Ещё 53 вопросов по теме — в тренажёре, с движком повторения
Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.
Частые вопросы
Нужны ли фреймворки для веба на Go?
Обычно достаточно стандартной библиотеки и тонкого роутера. На собесе ценят понимание, что даёт net/http само по себе: обработчики, middleware через обёртки, таймауты сервера.
Что такое табличные тесты?
Стиль, где случаи описаны срезом структур, а один цикл прогоняет их через общую проверку. Так тесты компактны, а добавление случая не требует копирования кода.