РЕАЛЬНОЕ СОБЕСЕДОВАНИЕ QA Engineer Ozon - Middle
Сегодня мы разберем живое техническое интервью на позицию мобильного тестировщика, в котором кандидат с честностью демонстрирует свой реальный опыт: уверенное владение базовыми инструментами (эмуляторы, сниферы, диплинки, пуши), но пробелы в работе с логами, аналитикой и формализацией тест-кейсов. Беседа показывает, как детальные вопросы интервьюера вскрывают зрелость процессов на прошлых проектах кандидата и умение (или неумение) мыслить как пользователь и как инженер, что делает это интервью показательным примером для разбора типичных ошибок джунов и мидлов.
Вопрос 1. В каких случаях выполняют ночные запуски автотестов и как оперативно реагируют на их результаты?
Таймкод: 00:07:18
Ответ собеседника: правильный. Ночные прогоны запускаются для разгрузки ресурсов и удобства, результаты анализируются уже в рабочее время, без срочных ночных реакций.
Правильный ответ:
Ночные запуски автотестов обычно являются частью полноценного CI/CD процесса и используются для выполнения более тяжелых, долгих и комплексных проверок, которые нецелесообразно запускать при каждом коммите или во время активного рабочего дня.
Ключевые сценарии, когда запускают ночные прогоны:
-
Комплексные регрессионные наборы:
- Большой пул UI/интеграционных/сквозных (end-to-end) тестов, которые занимают десятки минут или часы.
- Проверка на регрессии по всему продукту после серии изменений за день.
- Часто включают тесты, которые:
- нестабильны (flaky) и не крутятся на каждом коммите;
- требуют большого количества внешних сервисов или сложной среды.
-
Ресурсоемкие проверки:
- Нагрузочные/стресс-тесты (performance, нагрузка на БД, профилирование).
- Тесты, требующие выделенной инфраструктуры (отдельные стенды, фермы, дорогие окружения).
- Ночью проще использовать максимум ресурсов (CI-агенты, тестовая ферма), не мешая day-to-day разработке.
-
Кросс-среды и кросс-конфигурации:
- Прогоны на разных версиях сервисов, баз данных, конфигураций, фич-флагов.
- Проверка совместимости микросервисов, API контрактов, схем БД, миграций.
-
Метрики качества и технический контроль:
- Запуск расширенных статических анализаторов, линтеров, security-сканеров, проверок лицензий.
- Генерация отчетов по покрытию кода, техническому долгу, деградации производительности.
Реакция на результаты по времени:
- В большинстве зрелых процессов:
- Ночные прогоны не предполагают немедленного пробуждения команды.
- Результаты анализируются:
- утром: ответственными за качество (owner-ы сервисов, QA/разработчики);
- в дневное время: фиксация проблем, создание задач в трекере, определение приоритетов.
- Критические падения:
- Если ночные тесты выявили критический дефект в ветке, близкой к релизной:
- блокируется релиз до выяснения причин;
- может быть настроен авто-блок мёрджей или авто-rollback.
- Если инфраструктура или тесты сломались (а не продукт):
- задача уходит на починку пайплайна/окружения, но не воспринимается как продуктовый баг.
- Если ночные тесты выявили критический дефект в ветке, близкой к релизной:
Пример интеграции с CI (Go):
package main
import (
"log"
"os"
"time"
)
func main() {
// Условный пример nightly-режима: запускаем расширенный набор тестов
mode := os.Getenv("TEST_MODE")
if mode == "nightly" {
log.Println("Starting nightly regression suite...")
runFullRegression()
sendReport()
} else {
log.Println("Starting regular pipeline tests...")
runSmokeTests()
}
}
func runFullRegression() {
// Имитация долгих прогонов
time.Sleep(10 * time.Second)
log.Println("Full regression completed")
}
func runSmokeTests() {
time.Sleep(2 * time.Second)
log.Println("Smoke tests completed")
}
func sendReport() {
// Обычно: отправка в Allure/ReportPortal/Slack/Email
log.Println("Nightly report generated and sent")
}
Итоговая идея: ночные прогоны — это не про то, чтобы кто-то «вскакивал ночью», а про то, чтобы максимально эффективно использовать ресурсы и время, выполняя тяжелые и широкие проверки оффлайн, с разбором результатов в начале рабочего дня и автоматическим влиянием на качество релизов.
Вопрос 2. Кратко опиши свой последний проект, задачи и зону ответственности в тестировании.
Таймкод: 00:08:53
Ответ собеседника: правильный. Последний проект — мобильное банковское приложение с инвестиционным функционалом; занимался тестированием Android-версии на эмуляторе и реальном устройстве, iOS не покрывал из-за отсутствия техники и условий.
Правильный ответ:
При ответе на такой вопрос важно показать не только домен и стек, но и зрелость процессов, глубину участия, влияние на качество продукта и взаимодействие с командой.
Пример сильного ответа:
-
Проект:
- Мобильное банковское приложение с инвестиционным модулем: брокерский счет, торговля ценными бумагами, фонды, облигации, котировки в реальном времени, уведомления, интеграция с бэкендом банкинга, KYC/AML, безопасность.
- Микросервисная архитектура на бэкенде: API для авторизации, профиля, сделок, портфеля, отчетности, интеграции с биржами и внешними провайдерами.
-
Зона ответственности:
- Функциональное тестирование ключевых инвестиционных сценариев:
- открытие/пополнение брокерского счета;
- покупка/продажа акций, облигаций, фондов;
- обработка заявок, статусы сделок, отмены;
- корректное отображение портфеля, прибыли/убытков, комиссий, налогов.
- Интеграционное тестирование взаимодействия мобильного клиента с backend API:
- валидация контрактов (REST/JSON), кодов ответов, обработка ошибок;
- кросс-проверка данных между мобильным приложением, логами и базой данных (например, проверка фиксации сделок, остатков, движений по счету).
- Нефункциональные аспекты:
- устойчивость при нестабильном соединении (сетевые деградации, таймауты, повторные запросы);
- корректность обработки edge-case сценариев (нет котировок, задержки, частичная недоступность сервисов);
- базовые проверки производительности критичных операций (быстрая подгрузка портфеля, лента котировок).
- Функциональное тестирование ключевых инвестиционных сценариев:
-
Тестовая стратегия и подход:
- Разработка и поддержка тестовой документации:
- high-level тестовые сценарии для ключевых бизнес-процессов;
- чек-листы/регресс-матрицы под релизные циклы;
- фиксация граничных условий и рисковых зон (денежные переводы, сделки с реальными деньгами).
- Использование risk-based подхода:
- приоритизация тестов вокруг денежных операций, корректности расчетов и безопасности.
- Работа с тестовыми данными:
- подготовка реалистичных сценариев: разные типы клиентов, статусы KYC, лимиты, валюты, удержания комиссий и налогов.
- Разработка и поддержка тестовой документации:
-
Инструменты и практика:
- Мобильное тестирование:
- Android: тесты на реальных устройствах и эмуляторах;
- проверка разных версий ОС и экранов, push-уведомлений, deeplink-ов.
- API-тестирование:
- Postman/Insomnia/HTTP-клиенты для ручной проверки;
- автоматизация (если релевантно для собеседования на Go): написание сервисных тестов на Go для API.
Пример простого API-теста на Go для проверки критичного эндпоинта:
package main
import (
"encoding/json"
"net/http"
"testing"
)
type PortfolioResponse struct {
AccountID string `json:"account_id"`
Balance float64 `json:"balance"`
Currency string `json:"currency"`
}
func TestGetPortfolio(t *testing.T) {
resp, err := http.Get("https://api.bank.test/v1/portfolio?account_id=123")
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status: %d", resp.StatusCode)
}
var data PortfolioResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
t.Fatalf("decode failed: %v", err)
}
if data.AccountID != "123" {
t.Errorf("unexpected account id: %s", data.AccountID)
}
if data.Currency != "RUB" {
t.Errorf("unexpected currency: %s", data.Currency)
}
} - Мобильное тестирование:
-
Взаимодействие с командой:
- Плотная работа с разработчиками мобильного клиента и backend-сервисов:
- участие в разборе инцидентов, логировании, уточнении требований, API-контрактов;
- предложении улучшений UX и обработки ошибок.
- Взаимодействие с аналитиками и продуктом:
- уточнение бизнес-логики инвестпродуктов;
- контроль, что поведение приложения соответствует регуляторным требованиям и ожиданиям пользователей.
- Участие в релизном цикле:
- участие в регрессе перед релизом;
- проверка критичного функционала в релизных сборках;
- формирование go/no-go рекомендаций.
- Плотная работа с разработчиками мобильного клиента и backend-сервисов:
Такой ответ демонстрирует:
- понимание домена (финансы, инвестиции, риски);
- умение выстраивать тестовую стратегию;
- умение работать с API, данными и окружениями;
- влияние на надежность и безопасность продукта, а не только факт «тестировал мобильное приложение».
Вопрос 3. Почему ты занимался только тестированием Android-версии приложения и не тестировал iOS?
Таймкод: 00:10:04
Ответ собеседника: правильный. Тестировал только Android, так как имел под рукой Android-устройства и Windows, а технических условий для тестирования iOS не было.
Правильный ответ:
Корректный и достаточно зрелый ответ опирается не на личные предпочтения, а на организационные и технические ограничения.
Кратко по сути:
- Наличие инфраструктурных ограничений:
- Для iOS-разработки и тестирования нужны устройства под iOS и, как правило, macOS (Xcode, симуляторы, сборка, дебаг).
- Если в команде нет выделенного оборудования или доступа к нему, полноценно покрывать iOS невозможно.
- Разделение зон ответственности внутри команды:
- Команда могла быть разделена по платформам: один специалист отвечает за Android, другой — за iOS.
- Это позволяет глубже погружаться в особенности платформы (различия в UI-гайдах, поведении push-уведомлений, разрешениях, сетевых ограничениях).
- Практические причины:
- CI/CD и тестовые среды могли быть настроены в первую очередь под Android.
- Доступ к iOS-сборкам мог быть ограничен (Enterprise-сертификаты, распределение через TestFlight, политика безопасности).
Важно при ответе добавить, что:
-
Понимаешь особенности кросс-платформенного тестирования:
- Критичные бизнес-потоки (регистрация, логин, операции с деньгами, инвестиционные сделки) должны быть консистентны между Android и iOS.
- При наличии доступа к iOS-окружению ты бы:
- сверял поведение и интерфейс между платформами;
- проверял единообразие API-ответов, обработку ошибок и сценариев отказа;
- участвовал в регрессе обеих платформ для ключевых сценариев.
-
Готов при наличии инфраструктуры:
- Освоить инструменты под iOS (Xcode, симуляторы, device-farm, специфичные логи);
- Встроить iOS в существующую стратегию тестирования и автоматизации.
Такой ответ показывает:
- осознанность (это не «не хотел», а «не было ресурсов/зоны ответственности»);
- понимание требований к iOS-тестированию;
- готовность покрывать обе платформы при наличии технических условий.
Вопрос 4. Как была организована команда тестирования на проекте и почему завершилось твое участие?
Таймкод: 00:10:22
Ответ собеседника: правильный. В проекте было около 10 тестировщиков, по одному на модуль; в его команде было двое. Из-за сокращения бюджета оставили коллегу с устройствами под обе платформы, а его контракт завершили, так как он покрывал только Android.
Правильный ответ:
На такой вопрос важно кратко и структурировано описать:
- как организованы роли и зоны ответственности в команде тестирования;
- как устроено взаимодействие с кросс-функциональными командами;
- дать прозрачное и спокойное объяснение завершения участия без конфликтов и негативных формулировок.
Сильный вариант ответа:
-
Организация команды тестирования:
- Команда тестирования была встроена в продуктовые/фича-команды.
- Всего примерно 10 специалистов по качеству, распределенных по модулям:
- каждый модуль (платежи, инвестиции, профиль, onboarding, поддержка и т.д.) имел своего ответственного за качество;
- для сложных или критичных модулей могло быть 1–2 человека, чтобы обеспечить покрытие всех платформ или направлений.
- Роль тестировщиков включала:
- участие в планировании спринтов и refinement;
- анализ требований и проектирование тестов до разработки;
- тестирование фич на тестовых и предпроизводственных окружениях;
- участие в регрессионных прогонах перед релизом;
- взаимодействие с разработчиками, аналитиками, DevOps и поддержкой.
- Вертикали ответственности:
- функциональное тестирование ключевых бизнес-сценариев;
- интеграционное тестирование между мобильным приложением и backend-сервисами;
- частичное участие в автоматизации (API, UI, smoke/regression suites) там, где это было оправдано по ROI.
-
Взаимодействие внутри продуктовых команд:
- В каждой команде:
- backend-разработчики (часто на Go/Java/Kotlin и т.п.);
- мобильные разработчики (Android/iOS);
- тестировщик(и) как часть команды, а не внешняя функция.
- Процессы:
- CI/CD: автоматические сборки, smoke-тесты, nightly/regression;
- code review, тестирование по pull request/feature branch;
- быстрый цикл обнаружения и фикса багов.
- В каждой команде:
-
Причина завершения участия:
- Проект столкнулся с оптимизацией бюджета и ресурсами:
- было принято решение сократить количество людей, а не сворачивать продукт.
- приоритет отдали специалисту, который покрывал сразу две платформы (Android и iOS), имел доступ и к соответствующей технике.
- Формулировка:
- завершение сотрудничества связано с перераспределением ресурсов и оптимизацией бюджета, а не с качеством работы.
- свои задачи выполнял, покрывал Android-направление, участвовал в релизах, передал все знания и артефакты (тест-кейсы, сценарии, чек-листы, особенности модулей).
- Проект столкнулся с оптимизацией бюджета и ресурсами:
Такой ответ демонстрирует:
- понимание зрелых процессов обеспечения качества: встраивание в продуктовые команды, ответственность за модули, работу по спринтам, использование CI/CD;
- умение спокойно и профессионально объяснить завершение контракта как организационное решение, а не личную проблему;
- адекватное отношение к командной структуре и распределению зон ответственности.
Вопрос 5. Предоставляла ли компания тестовые устройства или доступ к фермам для мобильного тестирования?
Таймкод: 00:11:12
Ответ собеседника: правильный. Компания не предоставляла тестовые устройства и фермы и не оплачивала подобные ресурсы.
Правильный ответ:
При ответе на этот вопрос важно не просто констатировать факт отсутствия устройств или ферм, а показать понимание того, как в идеале должна быть организована инфраструктура для мобильного тестирования и какие риски возникают без нее.
Ключевые моменты:
-
Хорошая практика в мобильных проектах:
- Компания должна обеспечивать:
- реальными устройствами разных производителей, версий ОС, экранов;
- доступом к облачным фермам устройств (BrowserStack, AWS Device Farm, Firebase Test Lab и т.п.);
- возможностью тестировать push-нотификации, deeplink-и, работу в фоне, сетевые деградации.
- Это критично для:
- проверки стабильности и производительности;
- обнаружения платформенных багов;
- проверки UX на разных размерах экранов;
- тестирования edge-кейсов, специфичных для моделей и версий ОС.
- Компания должна обеспечивать:
-
Если компания не предоставляет устройства/ферму:
- Возникают риски:
- ограниченное покрытие платформ и версий ОС;
- повышенная вероятность того, что пользователи столкнутся с багами на конкретных устройствах;
- невозможность полноценно проверить iOS или редкие Android-устройства, если их нет у команды.
- При этом важно показать зрелую позицию:
- использовать доступные ресурсы максимально эффективно (эмуляторы, свои устройства);
- аргументированно доносить до менеджмента необходимость инфраструктуры для тестирования;
- фиксировать риски в релизной документации: какие устройства покрыты, какие нет.
- Возникают риски:
-
Как это может выглядеть в более зрелой среде:
-
Стенд и pipeline:
- CI запускает автоматические UI/API тесты на эмуляторах + части устройств фермы.
- Часть smoke/regression-прогонов идет на реальных девайсах.
-
Пример: интеграция автотестов с фермой устройств (схематично, Go):
package main
import (
"log"
"net/http"
"strings"
)
// Условный пример запуска тест-сессии на облачной ферме устройств через API
func startDeviceFarmSession(device, appURL string) error {
reqBody := strings.NewReader(`{
"device": "` + device + `",
"app_url": "` + appURL + `",
"test_suite": "regression"
}`)
resp, err := http.Post("https://device-farm.example/api/run", "application/json", reqBody)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted {
return err
}
log.Println("Device farm session started for", device)
return nil
}
func main() {
if err := startDeviceFarmSession("android-12-pixel-5", "https://storage/app.apk"); err != nil {
log.Fatalf("cannot start device farm session: %v", err)
}
}
-
-
Как лучше формулировать ответ на интервью:
- Честно указать, что компания не предоставляла инфраструктуру и это ограничивало покрытие.
- Показать, что понимаешь:
- как должно быть организовано тестирование в идеале;
- что отсутствие устройств — это управленческое и инфраструктурное ограничение, а не норма качества.
- Отметить, что при возможности:
- выступаешь за внедрение девайс-ферм;
- готов участвовать в выборе решений и интеграции их в CI/CD.
Такой подход демонстрирует техническую зрелость и понимание практик качественного мобильного тестирования, даже если на предыдущем месте работы инфраструктура была далека от идеала.
Вопрос 6. Как ты использовал Android Studio при тестировании: запускал ли эмулятор и просматривал логи приложения?
Таймкод: 00:11:57
Ответ собеседника: неполный. Использовал Android Studio для запуска приложения в эмуляторе, но не работал с логами приложения и не знал о такой возможности; серверные логи получал через поддержку.
Правильный ответ:
Для качественного тестирования мобильного приложения недостаточно просто запускать его в эмуляторе. Важно эффективно использовать инструменты Android Studio и экосистемы, чтобы:
- диагностировать проблемы;
- проверять корректность запросов/ответов;
- отслеживать ошибки, краши, логирование, работу с сетью.
Ключевые аспекты использования Android Studio в тестировании:
-
Запуск и конфигурация эмуляторов:
- Создание и настройка AVD (Android Virtual Device):
- разные версии Android (например, 8–14);
- разные размеры экранов, DPI, форм-факторы;
- проверка поведения приложения в разных условиях.
- Использование эмуляторов для:
- быстрых smoke-проверок новых сборок;
- воспроизведения дефектов;
- тестирования локализаций, ориентаций экрана, ограничений по памяти и сети.
- Создание и настройка AVD (Android Virtual Device):
-
Работа с логами (Logcat) — ключевой навык:
- Logcat позволяет отслеживать:
- ошибки приложения (Exception, stack trace, ANR, crash);
- логи уровня debug/info/warn/error;
- теги, добавленные разработчиками для бизнес-логики.
- Практическое применение:
- воспроизводишь баг → смотришь stack trace → определяешь компонент (экран, сервис, API).
- проверяешь, действительно ли отправился запрос, какие параметры, не упали ли сериализация/десериализация.
- Фильтрация:
- по имени пакета приложения;
- по уровню логирования;
- по тегу (например, "AUTH", "PAYMENTS", "INVEST", "API").
- Это:
- ускоряет коммуникацию с разработчиками;
- позволяет давать точные и воспроизводимые баг-репорты.
Пример: как разработчик может логировать важные события (тестировщик должен уметь это читать):
// Android-код
Log.d("AUTH", "Login request started for userId=$userId")
Log.e("AUTH", "Login failed", exception)Тестировщик в Logcat:
- фильтрует по тегу AUTH;
- видит, на каком шаге и с каким исключением произошел сбой.
- Logcat позволяет отслеживать:
-
Анализ сетевых запросов:
- Подходы:
- Использовать встроенные логи клиента (если приложение логирует HTTP-запросы/ответы).
- Использовать прокси-инструменты (Charles Proxy, Fiddler, mitmproxy) совместно с эмулятором:
- перенаправление трафика через прокси;
- анализ запросов (URL, headers, body, коды ответов);
- проверка корректности обработки ошибок (4xx, 5xx).
- В идеале:
- тестировщик понимает структуру API и может проверить консистентность:
- что отображаемые данные соответствуют данным из ответа сервера;
- что ошибки показываются пользователю корректно.
- тестировщик понимает структуру API и может проверить консистентность:
Пример простого Go-сервиса, который логирует входящие запросы (то, что полезно анализировать при тестировании):
package main
import (
"log"
"net/http"
)
func portfolioHandler(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
log.Printf("GET /portfolio user_id=%s", userID)
// Тестировщик по логам может проверить, что запрос дошел с нужными параметрами
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"balance": 100000, "currency": "RUB"}`))
}
func main() {
http.HandleFunc("/portfolio", portfolioHandler)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
} - Подходы:
-
Отладка проблем производительности и стабильности:
- Использование инструментов Android Studio:
- Profiler для анализа потребления CPU, памяти, сети;
- отслеживание утечек памяти, долгих операций на UI-потоке;
- анализ ANR и crash-репортов.
- В тестировании:
- запуск длительных сценариев (scroll, список операций, лента котировок);
- проверка, не растет ли память, не падает ли приложение при долгом использовании.
- Использование инструментов Android Studio:
-
Подход к ответу на интервью:
- Стоит показать, что:
- умеешь и считаешь обязательным работать с Logcat;
- понимаешь важность логов для воспроизведения и локализации дефектов;
- можешь, при необходимости, использовать прокси и анализировать сетевой трафик;
- готов самостоятельно углубляться в инструменты, а не ждать подсказок.
- Стоит показать, что:
Краткая, сильная формулировка:
- Использую Android Studio не только для запуска приложения на эмуляторе, но и:
- читаю логи через Logcat (ошибки, stack trace, бизнес-события);
- анализирую сетевые запросы и ответы;
- проверяю стабильность и производительность;
- помогаю разработчикам быстро локализовать и воспроизводить проблемы.
Такой уровень работы с инструментом сильно повышает качество тестирования и ценность специалиста в команде.
Вопрос 7. Что ты делаешь при неожиданном вылете мобильного приложения и корректно ли игнорировать невоспроизводимые вылеты?
Таймкод: 00:13:31
Ответ собеседника: неполный. Пытается воспроизвести вылет, фиксирует шаги и видео; если воспроизвести не удаётся — ничего не делает. Позже признаёт, что это неправильно и что нужно смотреть логи, но не умеет их снимать.
Правильный ответ:
Неожиданный вылет приложения (crash) — один из самых критичных видов дефектов в мобильных продуктах, особенно в финансовых/банковских/инвестиционных системах. Игнорировать даже «редкие» или «неустойчиво воспроизводимые» вылеты нельзя. Важно уметь:
- максимально зафиксировать контекст;
- собрать дополнительную техническую информацию;
- корректно эскалировать проблему, даже если стабильного сценария воспроизведения нет.
Подход к работе с вылетами:
-
Базовый алгоритм действий при краше:
- Сразу:
- зафиксировать:
- точное время вылета;
- устройство/эмулятор (модель, версия ОС);
- версию приложения;
- тип сети (Wi-Fi/4G/нет сети, VPN);
- окружение (dev/test/prod, тестовый/боевой пользователь);
- записать:
- последовательность действий вплоть до вылета (step-by-step);
- видео (если возможно), чтобы разработчик видел поведение интерфейса и тайминги.
- зафиксировать:
- Попробовать воспроизвести:
- повторить те же шаги;
- варьировать данные (другой аккаунт, другая сумма, другой сценарий, другая сеть);
- проверить на другом устройстве/эмуляторе;
- проверить на другой версии приложения, если релевантно.
- Сразу:
-
Работа с логами — обязательная практика: Даже если ты не разработчик, умение собирать и прикладывать логи — ключевая часть зрелого подхода.
Основные источники:
- Logcat (через Android Studio или adb):
- фильтрация по пакету приложения;
- поиск по словам "Exception", "Fatal", "Crash", "ANR".
- Серверные логи:
- корреляция по времени и user_id/trace_id/request_id;
- Встроенные crash-репортеры:
- Firebase Crashlytics, Sentry, Bugsnag и т.п.
Пример: как снять логи через adb при вылете (то, что должен уметь тестировщик):
# Фильтр по пакету приложения
adb logcat | grep com.example.appИли более точечно:
adb logcat *:EПосле воспроизведения:
- сохраняем фрагмент логов вокруг момента падения;
- прикладываем к задаче.
- Logcat (через Android Studio или adb):
-
Что делать с невоспроизводимыми вылетами: «Невоспроизводимый» не значит «несуществующий». Правильное поведение:
- Если воспроизвести не удалось:
- все равно заводим баг (как минимум с пометкой для наблюдения/triage):
- подробно описываем:
- контекст (см. выше);
- любые подозрения: асинхронные события, слабая сеть, push-уведомление, экран блокировки, смена языка, смена сети и т.п.;
- прикладываем видео, скриншоты, логи (если хоть что-то есть).
- подробно описываем:
- маркируем как:
- «требуется дополнительный анализ», «sporadic crash», «needs monitoring».
- все равно заводим баг (как минимум с пометкой для наблюдения/triage):
- Просим:
- подключить crash-репортер, если его нет;
- по signature (stack trace) найти похожие вылеты у реальных пользователей или на других инсталляциях.
Игнорировать такие вылеты:
- неправильно с точки зрения качества;
- дорого: один «редкий» вылет может бить по критическим сценариям (например, вылет при подтверждении операции или пополнении счета).
- Если воспроизвести не удалось:
-
Почему нельзя останавливаться только на шагах/видео: Только описание шагов без логов:
- часто недостаточно для локализации;
- увеличивает время анализа у разработчиков;
- может привести к решению «Cannot reproduce» без реального расследования.
Зрелый подход:
- минимум: шаги + контекст + видео;
- лучше: + клиентские логи (Logcat/adb), + id сессии/пользователя для поиска в серверных логах;
- идеал: наличие crash-репортинга, где по stack trace и event-данным можно увидеть первопричину.
-
Пример полезного бага по крашу (структура):
- Заголовок:
- "Crash при открытии экрана портфеля после восстановления из фона (Android 13, Pixel 6)"
- Описание:
- Шаги:
- Авторизоваться пользователем X.
- Открыть экран портфеля.
- Свернуть приложение на 5–10 минут.
- Вернуться в приложение через список последних.
- Фактический результат:
- Приложение вылетает.
- Ожидаемый результат:
- Приложение восстанавливает состояние без вылета.
- Шаги:
- Окружение:
- Android 13, Pixel 6, версия приложения 1.2.3 (build 456), стенд: test.
- Вложения:
- видео;
- фрагмент Logcat с stack trace (NullPointerException в PortfolioFragment);
- timestamp.
- Заголовок:
-
Связь с backend и Go-кодом: Краш часто может быть следствием некорректного ответа backend-а (пустые поля, неожиданный формат, 500-ошибка без обработки).
Пример проблемного Go-обработчика, который может приводить к нестабильному поведению клиента:
func portfolioHandler(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
if userID == "" {
// Плохая практика: неструктурированная ошибка
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("error"))
return
}
// ...
}Клиент без защитной логики может не ожидать такой ответ и падать на парсинге. Зрелый подход на backend:
- всегда возвращать предсказуемый JSON с кодом ошибки и сообщением;
- логировать проблемные запросы;
- это существенно упрощает диагностику вылетов.
Итоговая позиция:
- Игнорировать невоспроизводимые вылеты нельзя.
- Нужно максимально:
- зафиксировать контекст;
- собрать логи;
- эскалировать как наблюдаемый, но нестабильно воспроизводимый инцидент;
- добиваться использования crash-репортинга и улучшения логирования.
- Неспособность работать с логами — исправимый пробел, который нужно закрывать; для сильного специалиста работа с логами и техническими деталями — базовый обязательный навык.
Вопрос 8. Какие данные, помимо шагов и видео, нужно указывать при описании дефекта вылета приложения?
Таймкод: 00:16:58
Ответ собеседника: неполный. Считает, что «по уму» нужно указывать ожидаемое поведение, окружение, версию приложения и стенд, но на проекте фактически ограничивались коротким описанием и видео без полноценного багрепорта.
Правильный ответ:
Для инцидентов с вылетами приложения важно описывать дефект так, чтобы разработчик мог максимально быстро:
- понять контекст проблемы;
- воспроизвести её локально;
- увидеть первопричину по логам и коду.
Ограничиваться одной строкой и видео — недостаточно. Нужен структурированный багрепорт. Минимальный набор данных для краша/вылета:
-
Заголовок:
- Кратко и по сути, с контекстом:
- Примеры:
- "Crash при открытии экрана 'Портфель' после авторизации (Android 13)"
- "Вылет при подтверждении перевода после смены сети Wi-Fi→4G (iOS 16)"
-
Окружение: Уточняет, где и на чем наблюдается проблема:
- устройство:
- модель (Pixel 6, Samsung S21, Xiaomi 11 и т.п.);
- версия ОС (Android 12/13/14, iOS 15/16/17).
- тип устройства:
- реальное / эмулятор.
- версия приложения:
- номер версии и build (например, 3.5.1 (build 457));
- окружение/стенд:
- dev, test, stage, prod, preprod и т.п.
- сеть:
- Wi-Fi/4G/5G/VPN/прокси, переключения сети;
- доп. контекст:
- включен/выключен VPN;
- регион/язык системы, если релевантно.
- устройство:
-
Точное время и частота:
- когда произошел вылет (timestamp);
- единичный случай или повторяется;
- если повторяется:
- примерная частота: 2 из 5 попыток, каждый раз при таком сценарии, один раз в день и т.д.
-
Фактический результат:
- кратко и конкретно:
- "Приложение закрывается без сообщения об ошибке."
- "После нажатия 'Подтвердить' приложение вылетает на рабочий стол."
- "Появляется системное окно 'Приложение остановлено'."
- кратко и конкретно:
-
Ожидаемый результат:
- четко, без расплывчатости:
- "Операция завершается успешно, пользователь видит экран подтверждения."
- "Приложение не вылетает, отображается сообщение об ошибке с предложением повторить."
- четко, без расплывчатости:
-
Технические данные (максимально важный блок):
Даже если ты не пишешь код, ты должен уметь это запросить/снять.
- клиентские логи:
- Logcat (Android) или лог из Xcode/Crashlytics/Sentry/Bugsnag;
- фрагмент вокруг момента краша (stack trace, тип исключения, теги).
- идентификаторы для корреляции:
- user_id / session_id / request_id / trace_id (если есть);
- это позволяет backend-разработчикам найти инцидент в логах Go-сервиса, базы данных, очередей.
- crash-отчет:
- если подключен Crashlytics/Sentry:
- прикрепить ссылку на конкретный инцидент или stack trace.
- если подключен Crashlytics/Sentry:
Пример корректно приложенного stack trace (фрагмент):
FATAL EXCEPTION: main
Process: com.example.app, PID: 12345
java.lang.NullPointerException: Attempt to invoke virtual method 'getBalance()' on a null object reference
at com.example.app.portfolio.PortfolioFragment.render(PortfolioFragment.kt:58)Такой фрагмент сразу говорит разработчику, где смотреть.
- клиентские логи:
-
Дополнительные условия:
- состояние приложения:
- только что установили / обновили с предыдущей версии;
- восстановление из фона;
- смена темы, языка, аккаунта;
- действия в параллели:
- приход push-уведомления;
- входящий звонок;
- смена сети;
- блокировка/разблокировка экрана.
- наличие root/jailbreak (если релевантно политике безопасности).
- состояние приложения:
-
Пример хорошо оформленного бага на вылет:
- Заголовок:
- "Crash при открытии портфеля после восстановления приложения из фона (Android 13, Pixel 6)"
- Окружение:
- Device: Pixel 6
- OS: Android 13
- App: 3.5.1 (build 457)
- Env: STAGE
- Network: Wi-Fi, без VPN
- Шаги:
- Авторизоваться под тестовым пользователем T123.
- Открыть экран "Портфель".
- Свернуть приложение на 10 минут.
- Вернуться в приложение из списка последних.
- Фактический результат:
- Приложение вылетает на рабочий стол без сообщения.
- Ожидаемый результат:
- Экран "Портфель" успешно открывается, приложение не вылетает.
- Логи:
-
timestamp: 2025-05-01 10:23:45
-
Logcat (фрагмент):
FATAL EXCEPTION: main
java.lang.NullPointerException: portfolio == null
at com.example.app.portfolio.PortfolioFragment.render(PortfolioFragment.kt:58)
-
Уже из этого видно: проблема в обработке null-данных после восстановления.
- Заголовок:
-
Связь с backend (Go) и устойчивостью: Если backend возвращает неожиданные данные/ошибки, приложение не должно падать.
Пример устойчивого обработчика на Go (то, что ожидается с серверной стороны):
type Portfolio struct {
Positions []Position `json:"positions"`
}
func GetPortfolio(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
if userID == "" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"missing user_id"}`))
return
}
portfolio, err := loadPortfolio(userID)
if err != nil {
// Структурированная ошибка вместо "сломанных" данных
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":"internal_error"}`))
return
}
// Даже если у пользователя нет позиций, возвращаем корректный объект
if portfolio == nil {
portfolio = &Portfolio{Positions: []Position{}}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(portfolio)
}Если тестировщик видит, что клиент падает на пустом/нестандартном ответе — это отличный кейс для репорта: и к мобильным, и к backend.
Вывод:
- Грамотное описание дефекта вылета — это:
- шаги + видео +
- подробное окружение +
- четкий expected/actual +
- технические артефакты (логи, id, время).
- Такой подход позволяет быстро локализовать и исправить критические дефекты и демонстрирует глубокое понимание процесса обеспечения качества.
Вопрос 9. Предпринимал ли ты попытки улучшить процессы тестирования и оформления багов на прошлом проекте?
Таймкод: 00:17:39
Ответ собеседника: неполный. Обсуждал проблемы только с коллегой, инициатив по системным изменениям почти не проявлял, объяснял это слабой компетенцией руководства.
Правильный ответ:
На такой вопрос важно показать не позицию «наблюдателя» или «жертвы процессов», а умение в рамках своей зоны влияния:
- предлагать улучшения,
- аргументировать их ценность,
- внедрять хотя бы частичные изменения,
- фиксировать проблемы в конструктивной форме.
Даже если руководство слабое или сопротивляется, зрелый специалист демонстрирует инициативу, а не пассивность.
Оптимальная стратегия и примеры корректного поведения:
-
Локальные инициативы в своей команде (минимум, который должен быть): Даже без формальной поддержки сверху можно:
- Ввести у себя базовый стандарт создания багрепортов:
- структура: заголовок, окружение, шаги, ожидаемый/фактический результат, вложения (логи, видео).
- использовать шаблоны задач в трекере (Jira, YouTrack, Trello и т.п.).
- Начать прикладывать:
- логи (клиентские и при возможности серверные);
- временные метки;
- идентификаторы пользователей/запросов;
- корректную приоритизацию (Severity/Impact).
- Делать это последовательно, даже если от тебя формально «не требуют».
- Показать на реальных примерах, что такие баги правятся быстрее и качественнее — это лучший аргумент.
- Ввести у себя базовый стандарт создания багрепортов:
-
Инициирование улучшений на уровне команды/проекта: Вместо жалоб на «некомпетентность» менеджмента, правильный подход:
-
Предложить конкретные улучшения:
- шаблон баг-репортов;
- чек-листы по регрессии;
- минимальные критерии приемки (Definition of Done/Ready);
- обязательное указание версии приложения, стенда, устройства, ОС.
-
Оформлять инициативы в конструктивном виде:
- короткий документ/страницу в Confluence/Notion: «Как мы оформляем баги, чтобы их чинили быстрее»;
- пример 3–5 хорошо оформленных задач с быстрым временем реакции.
-
Не навязывать «сверху вниз», а показывать пользу:
- меньше времени на уточнение деталей;
- меньше нерелевантных вопросов от разработчиков;
- меньше нерепродуцируемых багов.
-
-
Работа с руководством и «сложными» менеджерами:
- Важно не переводить разговор в плоскость «они некомпетентны», а выстраивать диалог через:
- риск: "Сейчас у нас нет нормального оформления багов, из-за этого критические дефекты могут уходить в прод."
- метрики: "Если структурировать баги, мы сократим время анализа и количество возвратов задач."
- Формулировки:
- не: «Вы всё делаете неправильно»;
- а: «Есть несколько небольших изменений, которые повысят скорость и качество. Давайте попробуем на одном модуле/спринте».
- Важно не переводить разговор в плоскость «они некомпетентны», а выстраивать диалог через:
-
Примеры конкретных улучшений, которые можно было (и нужно) предлагать:
- Шаблон баг-репорта в трекере:
- поля:
- Environment (env/stage),
- App version,
- Device/OS,
- Steps,
- Expected result,
- Actual result,
- Attachments (video/logs/screenshots),
- Severity/Priority.
- поля:
- Внедрение минимального процесса triage:
- регулярный разбор критичных багов;
- классификация по приоритетам;
- договоренность, что без ключевых полей баг возвращается на доработку.
- Инициатива по логам и crash-репортингу:
- предложить подключить Crashlytics/Sentry;
- объяснить, как это снизит время поиска причин вылетов.
- Шаблон баг-репорта в трекере:
-
Связь с backend и Go-контекстом (как «продать» улучшения технарям): Например, можно инициировать:
- Согласованный формат логирования и trace-id, чтобы проще связывать:
- запросы мобильного клиента;
- логи Go-сервисов;
- инциденты на фронте.
Пример (Go):
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
traceID := r.Header.Get("X-Request-ID")
if traceID == "" {
traceID = generateTraceID()
}
// Логируем с traceID
log.Printf("trace_id=%s method=%s path=%s", traceID, r.Method, r.URL.Path)
// Прокидываем дальше
ctx := context.WithValue(r.Context(), "trace_id", traceID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Тестировщик, предлагая использовать
X-Request-IDи указывая его в баг-репортах, помогает быстро находить нужные логи на backend. - Согласованный формат логирования и trace-id, чтобы проще связывать:
-
Как это сформулировать на интервью:
Вместо:
- «Я ничего не делал, потому что руководство некомпетентное»,
Лучше сказать, что:
- В своей зоне ответственности:
- старался оформлять баги структурированно;
- использовал шаблоны/минимальный стандарт;
- предлагал коллегам и команде аналогичный подход.
- Пытался:
- поднимать вопросы качества багрепортов и процессов;
- аргументировать через примеры и риски.
- Понимаешь, что:
- инициатива по улучшению процессов — часть твоей роли;
- даже при слабом менеджменте можно улучшить хотя бы локальные практики.
Такой ответ демонстрирует:
- проактивность и ответственность за качество не только кода, но и процессов;
- умение действовать конструктивно в сложной среде;
- ориентацию на результат, а не на оправдания.
Вопрос 10. Приведи примеры использования снифера (Fiddler): для чего ты его применял и какие функции использовал.
Таймкод: 00:20:06
Ответ собеседника: правильный. Использовал Fiddler для перехвата запросов и анализа проблем, а также для подмены JSON-ответов через breakpoints, чтобы проверять отображение элементов и поведение интерфейса без изменения БД и без готового бэкенда; приводит примеры с портфелем акций и персональным менеджером.
Правильный ответ:
Использование HTTP-прокси (Fiddler, Charles, mitmproxy и аналогов) — один из ключевых инструментов для глубокого тестирования клиент–серверных приложений. Важно уметь применять его не только для «посмотреть запросы», но и для:
- валидации корректности интеграции клиента и backend;
- тестирования edge-case сценариев;
- негативных сценариев и ошибок;
- проверки устойчивости UI к любым вариантам ответов.
Ключевые, технически зрелые сценарии использования Fiddler:
-
Анализ запросов и ответов (основа):
- Проверка, что мобильное/веб-приложение:
- ходит на правильные endpoint-ы;
- использует корректные HTTP-методы (GET/POST/PUT/DELETE и т.п.);
- отправляет обязательные заголовки (авторизация, content-type, trace-id);
- передает корректные параметры (user_id, account_id, фильтры, пагинация).
- Проверка ответов:
- статус-коды (200, 201, 400, 401, 403, 404, 500 и т.п.);
- структура JSON соответствует контракту (schema, типы, nullable-поля);
- данные в UI соответствуют данным из API.
Это позволяет:
- быстро находить рассинхроны между фронтом и бэкендом;
- отличать баг клиента от бага сервера.
- Проверка, что мобильное/веб-приложение:
-
Тестирование негативных и пограничных сценариев через подмену ответов: Использование breakpoints / autoresponder / modifying on the fly:
- Подмена тела успешного ответа:
- Проверка, как фронт обрабатывает:
- пустые массивы (нет позиций в портфеле, нет транзакций);
- большие массивы (много записей);
- null-ы, отсутствующие поля, неожиданные значения.
- Проверка, как фронт обрабатывает:
- Подмена кодов ответа:
- 400/422 — валидационные ошибки: корректно ли UI показывает сообщение;
- 401/403 — просроченный токен/нет прав: происходит ли разлогин, обновление токена, информирование пользователя;
- 404 — нет ресурса: не падает ли экран, есть ли fallback;
- 500/502/504 — серверные ошибки и таймауты: есть ли retry, дружелюбное сообщение, отсутствие краша.
- Подмена бизнес-данных:
- наличие персонального менеджера/нет менеджера;
- разные типы тарифов, лимитов, статусов KYC;
- сложные кейсы: блокированный счет, маржинальные позиции, дефолт по облигации и т.п.
Это особенно ценно:
- когда backend-функционал еще не реализован или сложен в настройке;
- когда нельзя/неудобно менять данные в базе.
- Подмена тела успешного ответа:
-
Тестирование устойчивости клиента к некорректным данным: Fiddler позволяет эмулировать то, что рано или поздно произойдет в реальной системе.
- Примеры:
- обязательное поле отсутствует;
- тип поля не соответствует контракту (строка вместо числа);
- очень длинные строки в имени, описании;
- спецсимволы, emoji, RTL-тексты;
- дублирующиеся ключи, лишние поля.
- Цель:
- убедиться, что приложение:
- не падает (нет крашей);
- корректно обрабатывает частичные данные;
- показывает пользователю понятные сообщения.
- убедиться, что приложение:
- Примеры:
-
Диагностика проблем авторизации и безопасности:
- Проверка:
- как клиент добавляет токен (Bearer, cookies, custom headers);
- что происходит при истечении токена: запрос на refresh, разлогин, сообщения.
- Негатив:
- подмена токена на некорректный;
- удаление заголовка Authorization;
- проверка, что backend корректно отвечает 401/403, а клиент правильно реагирует.
- Базовые security-проверки:
- убедиться, что чувствительные данные не уходят в открытом виде;
- отсутствие логина/пароля в query-параметрах;
- отсутствие персональных данных в URL и логах.
- Проверка:
-
Эмуляция сетевых проблем: В некоторых инструментах (Charles/mitmproxy; Fiddler частично через rules/latency):
- добавление задержек (latency) к ответам;
- обрыв соединения;
- нестабильная сеть.
- Цель:
- проверить поведение клиента:
- спиннеры, повторные попытки, timeouts;
- нет ли фризов/крашей при плохой сети.
- проверить поведение клиента:
-
Взаимодействие с backend и Go-сервисами: При тестировании Go-бэкенда через Fiddler можно:
- Проверять соответствие API-реализации спецификации (OpenAPI/Swagger):
- правильные поля, типы, коды ответов.
- Воспроизводить запросы, которые делает клиент:
- фиксировать их в Fiddler;
- повторять и модифицировать руками;
- искать баги в обработчиках.
Пример простого Go-обработчика, под который удобно гонять запросы через Fiddler:
package main
import (
"encoding/json"
"log"
"net/http"
)
type Portfolio struct {
ClientID string `json:"client_id"`
Assets []string `json:"assets"`
}
func portfolioHandler(w http.ResponseWriter, r *http.Request) {
clientID := r.URL.Query().Get("client_id")
if clientID == "" {
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(map[string]string{
"error": "missing client_id",
})
return
}
resp := Portfolio{
ClientID: clientID,
Assets: []string{"AAPL", "GOOGL", "TSLA"},
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("encode error: %v", err)
}
}
func main() {
http.HandleFunc("/api/portfolio", portfolioHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}Через Fiddler:
- можно подменять ответы этого сервиса (например, пустые Assets или 500-ошибку);
- проверять, что фронт не ломается и корректно отображает состояние портфеля.
- Проверять соответствие API-реализации спецификации (OpenAPI/Swagger):
-
Как это правильно подать на интервью: Сильный ответ должен показать, что ты:
- используешь Fiddler не только «посмотреть трафик», но и:
- валидируешь контракты;
- тестируешь edge-cases и ошибки;
- ускоряешь тестирование фич до готовности backend;
- помогаешь разработчикам воспроизводить и локализовывать дефекты.
- понимаешь ценность:
- подмены ответов;
- анализа заголовков;
- проверки безопасности;
- работы с нестабильной сетью.
- используешь Fiddler не только «посмотреть трафик», но и:
Такой подход демонстрирует глубокое понимание клиент–серверного взаимодействия и умение использовать инструменты сниффинга как часть полноценной стратегии тестирования.
Вопрос 11. Какие элементы HTTP-ответа ты изменял при тестировании через Fiddler и как именно это делал?
Таймкод: 00:23:25
Ответ собеседника: правильный. В основном подменял тело ответа (body) через breakpoints, иногда используя шаблоны Fiddler; при подменах статус-код также корректно менялся.
Правильный ответ:
При использовании Fiddler (или аналогичных инструментов) важно понимать, что можно и нужно управлять не только телом ответа, но и статус-кодами, заголовками и задержками. Это позволяет полноценно тестировать устойчивость клиента, корректность обработки ошибок и соответствие API-контракту.
Ключевые элементы HTTP-ответа, которые полезно модифицировать:
-
Тело ответа (Body) Основной и ожидаемый сценарий.
Для чего:
- Проверка отображения различных наборов данных без изменения БД.
- Тестирование edge-case сценариев:
- пустые списки;
- большое количество элементов;
- специфичные бизнес-состояния (заблокированный счет, персональный менеджер, разные статусы заявок);
- невалидные или нестандартные значения (0, отрицательные, очень большие числа, редкие валюты).
Как:
- Включить breakpoints (Before Responses).
- Изменить JSON в окне редактора:
- добавить/удалить поля;
- изменить значения;
- эмулировать разные состояния.
- Нажать Run to Completion, чтобы отправить модифицированный ответ клиенту.
Пример: Было:
{
"hasPersonalManager": false
}Сделали в Fiddler:
{
"hasPersonalManager": true,
"managerName": "Иван Иванов",
"managerPhone": "+7-900-000-00-00"
}Проверяем: UI корректно отрисовывает блок менеджера.
-
Статус-код (Status Code) Критично для проверки обработки ошибок и устойчивости клиента.
Для чего:
- Проверить реакцию приложения на:
- 400/422 — ошибки валидации;
- 401/403 — проблемы авторизации/прав;
- 404 — отсутствующий ресурс;
- 500/502/503/504 — внутренние/сетевые ошибки.
Как:
- На breakpoint в Response:
- изменить
oSession.responseCode = 500;или через интерфейс; - при необходимости скорректировать тело под формат ошибки.
- изменить
- Проверить:
- не падает ли клиент;
- показывает ли корректное сообщение;
- выполняет ли retry там, где требуется.
Пример негативного ответа:
{
"error": "internal_error",
"message": "Service temporarily unavailable"
} - Проверить реакцию приложения на:
-
Заголовки (Headers) Часто игнорируются, но очень важны.
Для чего:
- Проверка:
- кэширования (Cache-Control, ETag);
- CORS (Access-Control-Allow-Origin);
- контента (Content-Type: application/json / text/html);
- авторизации (WWW-Authenticate);
- версионности API, feature-флагов.
- Тест:
- как клиент ведет себя при неправильном/отсутствующем Content-Type;
- что будет, если приходит неожиданная кодировка или не тот MIME-тип.
Как:
- На breakpoint редактировать headers:
- добавлять/удалять/менять значения.
- Примеры:
- удалить
Content-Type: application/jsonи проверить, не ломается ли парсер; - поменять
Cache-Controlи убедиться, что клиент не кэширует чувствительные данные.
- удалить
- Проверка:
-
Искусственные задержки и сбои (если инструмент позволяет) Даже если делается не напрямую в Fiddler, концептуально это обязательная часть зрелого тестирования.
Для чего:
- Проверить поведение:
- при долгих ответах (slow API);
- при обрыве соединения;
- при частичных ответах.
Если использовать FiddlerScript:
- можно добавить задержку:
if (oSession.HostnameIs("api.example.com")) {
oSession["request-trickle-delay"] = "300";
oSession["response-trickle-delay"] = "1000";
}Проверяем:
- не блокируется ли UI;
- корректно ли отображаются лоадеры;
- есть ли таймауты и обработка ошибок.
- Проверить поведение:
-
Интеграция с backend-контрактами (на примере Go) При тестировании Go-сервисов через Fiddler важно проверять, что клиент:
- корректно обрабатывает предусмотренные API-ответы;
- не падает при неожиданных ответах.
Пример ожидаемого обработчика на Go:
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message"`
}
func handler(w http.ResponseWriter, r *http.Request) {
// ...
if badRequest {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(ErrorResponse{
Error: "invalid_input",
Message: "amount must be positive",
})
return
}
// ...
}Через Fiddler можно:
- подменить 200 на 400;
- оставить тело без ожидаемых полей;
- убедиться, что клиент:
- не крашится;
- показывает пользователю понятную ошибку.
Вывод:
- Правильное использование Fiddler включает:
- модификацию body;
- управление статус-кодами;
- работу с заголовками;
- эмуляцию задержек и сбоев.
- Это дает возможность полноценно тестировать:
- устойчивость клиента;
- корректность обработки ошибок;
- соответствие API-контракту;
- поведение UI в сложных, редких и пограничных сценариях без вмешательства в реальный backend и БД.
Вопрос 12. Есть ли у тебя опыт тестирования продуктовой аналитики (воронки, события) и знаний о системах для хранения аналитики?
Таймкод: 00:24:54
Ответ собеседника: неправильный. Говорит, что не тестировал аналитику и не знает, где она хранится.
Правильный ответ:
Тестирование продуктовой аналитики является неотъемлемой частью качества современного продукта. Ошибки в событиях аналитики приводят к неверным продуктовым решениям, искаженному A/B-тестированию, неправильной оценке эффективности фич и маркетинговых кампаний. Игнорировать это направление нельзя.
Ниже — системный подход к тестированию аналитики, который ожидается.
Основные понятия продуктовой аналитики:
- События (events):
- фиксируют действия пользователя: просмотр экрана, клик по кнопке, успешная/неуспешная операция, скролл до блока, выбор тарифа и т.п.
- Параметры (properties):
- дополнительные данные события: user_id, session_id, тип устройства, версия приложения, тариф, сумма операции, источник трафика и др.
- Воронки:
- последовательности шагов, которые пользователь должен пройти:
- пример: установка → запуск → регистрация → KYC → пополнение счета → первая сделка.
- последовательности шагов, которые пользователь должен пройти:
- Пользовательские атрибуты:
- статические/долгоживущие характеристики: регион, тип клиента, сегмент, наличие персонального менеджера и др.
Что и как нужно тестировать:
-
Соответствие требованиям/спецификации событий:
- Обычно существует:
- аналитическая спецификация или tracking plan (в Confluence/Notion/Docs), где описано:
- какое событие отправляется;
- при каком действии;
- с какими параметрами;
- в какой системе оно должно оказаться.
- аналитическая спецификация или tracking plan (в Confluence/Notion/Docs), где описано:
- Тестирование:
- для каждого ключевого сценария (регистрация, логин, платеж, сделка, отмена, ошибка) проверить:
- событие отправляется;
- имя события соответствует спецификации;
- все обязательные параметры присутствуют;
- типы и значения параметров корректны;
- нет лишних личных данных (GDPR/152-ФЗ — приватность).
- для каждого ключевого сценария (регистрация, логин, платеж, сделка, отмена, ошибка) проверить:
- Обычно существует:
-
Целостность воронок:
- Важно проверять не только отдельные события, но и их последовательность.
- Примеры:
- онбординг:
app_open→view_onboarding→skip_onboardingилиcomplete_onboarding; - инвестиции:
open_portfolio→open_instrument→create_order→order_success/order_failed.
- онбординг:
- Тестирование:
- пройти реальный сценарий и убедиться, что:
- все события пришли;
- нет разрывов;
- параметры связывают шаги одной сессии/пользователя (user_id, session_id).
- пройти реальный сценарий и убедиться, что:
-
Проверка корректности параметров:
- Критично для аналитики и сегментаций.
- Примеры параметров:
user_id,account_id— должны совпадать с реальным тестовым пользователем;screen_name,feature_flag,ab_group,tariff,device_model,os_version;- бизнес-параметры: суммы, валюты, типы операций, результаты (success/fail, причина отказа).
- Ошибки:
- пустые/0 значения;
- неверные типы;
- неправильные юнит-экономические поля → искаженная аналитика.
-
Где и как смотреть аналитику:
В реальных проектах используются системы:
- продуктовая аналитика:
- Amplitude, Mixpanel, AppMetrica, Firebase Analytics, AppsFlyer, Adjust и др.
- хранилища/BI:
- ClickHouse, BigQuery, Snowflake, Redshift, PostgreSQL, Vertica;
- поверх них: Looker, Power BI, Tableau, Metabase, Superset.
Подход к тестированию:
- На уровне клиента:
- смотреть запросы аналитики через:
- Fiddler/Charles/mitmproxy;
- логирование в консоль/Logcat;
- debug-режим SDK (у многих аналитических SDK есть).
- смотреть запросы аналитики через:
- На уровне бэкенда/хранилища:
- проверять, что события корректно попадают в целевую систему.
- выполнять выборки по тестовому user_id/session_id.
Пример простой проверки события в ClickHouse (SQL):
SELECT event_name,
user_id,
properties["screen"],
properties["result"],
event_time
FROM analytics_events
WHERE user_id = 'test_user_123'
AND event_time >= now() - INTERVAL 1 HOUR
ORDER BY event_time;Здесь:
- проверяем наличие нужных событий и параметров для тестового пользователя.
- продуктовая аналитика:
-
Проверка приватности и безопасности аналитики:
- Важно убедиться, что:
- в аналитику не утекают:
- полные номера карт;
- CVV;
- пароли;
- паспортные данные;
- избыточные персональные данные.
- в аналитику не утекают:
- Тестирование:
- просматриваем события;
- убеждаемся, что используются id/токены/маскирование там, где необходимо.
- Важно убедиться, что:
-
Связь с backend и Go-примерами:
Часто аналитика реализуется на backend или проксируется через него.
Пример простого события на Go:
type AnalyticsEvent struct {
Name string `json:"name"`
UserID string `json:"user_id"`
Properties map[string]string `json:"properties"`
Timestamp time.Time `json:"timestamp"`
}
func TrackEvent(e AnalyticsEvent) {
// Здесь могла бы быть отправка в Kafka / ClickHouse / сторонний сервис
log.Printf("analytics: name=%s user=%s props=%v ts=%s",
e.Name, e.UserID, e.Properties, e.Timestamp.Format(time.RFC3339))
}
func HandleOrderSuccess(userID, orderID string) {
// бизнес-логика...
TrackEvent(AnalyticsEvent{
Name: "order_success",
UserID: userID,
Properties: map[string]string{
"order_id": orderID,
"source": "mobile",
},
Timestamp: time.Now(),
})
}Что должен проверить тестировщик:
- событие
order_successреально отправляется при успешном заказе; - user_id и order_id корректны;
- событие содержит
source=mobileдля правильной сегментации.
- событие
-
Как это правильно озвучить на интервью:
Сильный ответ включает:
- Да, тестировал:
- корректность отправки событий на ключевых действиях;
- структуру payload-ов аналитики;
- соответствие tracking plan-у;
- целостность воронок.
- Использовал:
- прокси-сниферы для перехвата событий;
- доступ к аналитической БД или кабинетам систем (Amplitude/Firebase/AppMetrica и т.п.);
- SQL-запросы для точечной проверки.
- Понимаю:
- влияние аналитики на продуктовые решения;
- важность корректности и полноты данных;
- требования к приватности и безопасности данных в аналитике.
- Да, тестировал:
Если практического опыта не было, корректная позиция:
- продемонстрировать понимание вышеописанного подхода;
- подчеркнуть готовность и способность быстро подключиться к тестированию аналитики;
- не оставлять эту область «за бортом» как что-то второстепенное.
Вопрос 13. Какой у тебя опыт тестирования push-уведомлений и какие особенности необходимо проверять?
Таймкод: 00:26:10
Ответ собеседника: неполный. Проверял получение push-уведомлений при достижении цены акций, корректность текста и переход на нужный экран из трёх состояний приложения (активно, свернуто, выгружено), а также что уведомления не запрещены в настройках. Не углублялся в типы пушей, механику разрешений, причины неприхода, часть вопросов отдавал другим командам.
Правильный ответ:
Тестирование push-уведомлений — критичный аспект для финансовых, инвестиционных и любых продуктовых приложений, где уведомления влияют на поведение пользователя (алерты по ценам, ордерам, транзакциям, безопасности, маркетинговые акции). Важно понимать архитектуру, типы уведомлений, точки отказа и поведение на разных платформах и состояниях приложения.
Основные аспекты, которые необходимо учитывать и тестировать.
Понимание цепочки доставки push-уведомлений:
Типичная схема:
- Backend-приложения (часто Go-сервисы) формируют событие (например, сработал триггер по цене/ордера).
- Сервис уведомлений:
- для Android: FCM (Firebase Cloud Messaging);
- для iOS: APNs;
- иногда используется свой gateway или сторонний провайдер.
- На устройстве:
- системный сервис получает push;
- передает приложению (в зависимости от типа и состояния);
- приложение отображает уведомление, обрабатывает клик, выполняет deep link и т.п.
Тестировщик должен:
- понимать эту цепочку логически;
- уметь сузить область проблемы (backend, провайдер, токен, настройки OS, логика клиента).
Типы push-уведомлений и что тестировать:
-
Транзакционные и критичные:
- примеры:
- подтверждение операции;
- исполнение/отмена ордера;
- безопасность (вход с нового устройства, смена пароля).
- требования:
- всегда доставляются корректно;
- содержат точную информацию;
- ведут на правильный экран (детали операции/ордера);
- без орфографических, юридических и смысловых ошибок.
- примеры:
-
Информационные:
- новости, статусы заявок, изменения условий, напоминания.
- проверяем:
- не спамят ли;
- корректная персонализация;
- актуальность контента.
-
Маркетинговые:
- акции, предложения, кросс-продажи.
- проверяем:
- соблюдение opt-in/opt-out;
- корректную сегментацию;
- соответствие настройкам пользователя.
-
Silent/Background push:
- содержат данные для фонового обновления без UI-уведомления.
- проверяем:
- не появляются в шторке;
- изменения данных происходят корректно;
- не приводят к лишним пробуждениям, батарейному дрену, крашам.
Ключевые сценарии тестирования push-уведомлений:
-
Состояния приложения: Обязательно проверяются все варианты:
- активно (foreground);
- свернуто (background);
- полностью выгружено (killed). Для каждого:
- уведомление приходит;
- отображается ожидаемым образом (шторка/баннер/внутренний баннер в приложении);
- клик по уведомлению:
- открывает нужный экран;
- корректно обрабатывает данные (id ордера, инструмента, операции).
-
Deep link / навигация:
- Проверить:
- переход по пушу:
- из активного приложения;
- из бекграунда;
- из полностью закрытого.
- корректное открытие:
- конкретного экрана (ордер, портфель, чат, акция);
- с правильными параметрами (order_id, ticker, campaign_id).
- переход по пушу:
- Важно:
- отсутствие «битых» экранов;
- отсутствие крашей при открытии по устаревшему/невалидному payload-у.
- Проверить:
-
Текст и контент:
- Проверяем:
- локализацию (язык системы/приложения);
- формат дат, сумм, валют;
- отсутствие чувствительных данных (номер карты целиком, паспорт, CVV);
- длина текста в разных устройствах (не обрезается ключевая информация);
- соответствие юридическим требованиям, если уведомление про финансы.
- Проверяем:
-
Настройки уведомлений: Нужно различать:
- Настройки ОС:
- пользователь мог отключить уведомления для приложения;
- проверяем корректное поведение:
- если пуши отключены — уведомлений нет;
- приложение может информировать о необходимости включить.
- Настройки внутри приложения:
- категории уведомлений:
- торговые сигналы;
- операции;
- маркетинг;
- безопасность.
- проверяем:
- включение/отключение отдельных типов работает корректно;
- изменения сохраняются и учитываются backend-ом;
- пользователь не получает уведомления по отключенным категориям.
- категории уведомлений:
- Настройки ОС:
-
Причины неприхода пушей, которые нужно понимать: Даже если тестировщик не управляет инфраструктурой, он должен уметь диагностировать.
Основные причины:
- неверный/устаревший device token;
- смена устройства или переустановка приложения;
- отключены уведомления на уровне ОС;
- агрессивная оптимизация батареи/фоновой работы;
- сетевые проблемы;
- ошибки на backend (не отправили, неверный payload, превышен лимит);
- блок со стороны FCM/APNs (некорректный ключ, сертификат).
Практика:
- проверять логи backend-а и провайдера (если есть доступ);
- сверять, был ли пуш отправлен и с каким статусом;
- проверять device token в логах/кабинете.
Пример логики отправки push из backend на Go (упрощенно):
type PushPayload struct {
Title string `json:"title"`
Body string `json:"body"`
Type string `json:"type"` // e.g. "order_executed"
Data map[string]string `json:"data"`
}
func SendPushToFCM(token string, payload PushPayload) error {
body, _ := json.Marshal(map[string]interface{}{
"to": token,
"notification": map[string]string{
"title": payload.Title,
"body": payload.Body,
},
"data": payload.Data,
})
req, _ := http.NewRequest("POST", "https://fcm.googleapis.com/fcm/send", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "key="+os.Getenv("FCM_SERVER_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return fmt.Errorf("fcm error: %s", string(b))
}
return nil
}
Что важно тестировать с точки зрения клиента:
- корректность payload-а:
type,data.order_id,data.tickerи др. приходят и используются;
- устойчивость:
- если payload частично битый — приложение не падает.
SQL-пример проверки регистрации устройства для пушей:
SELECT user_id, device_id, push_token, platform, updated_at
FROM push_subscriptions
WHERE user_id = 'test_user_123';
Здесь тестировщик проверяет:
- зарегистрирован ли токен;
- та ли платформа;
- обновляется ли запись при переустановке приложения.
Как это оформить на интервью:
Сильный ответ должен показать, что ты:
- проверяешь пуши:
- во всех состояниях приложения;
- с корректной навигацией и deep link-ами;
- с учетом пользовательских и системных настроек;
- понимаешь причины, по которым пуши могут не приходить, и умеешь сузить область:
- токен, настройки, сеть, backend, провайдер;
- следишь за:
- корректностью текста и локализации;
- безопасностью (без утечек чувствительных данных);
- правильной категоризацией (opt-in/opt-out для маркетинга);
- при необходимости:
- смотришь сетевой трафик (Fiddler/Charles) для проверки отправки/получения;
- используешь логи и аналитические системы для валидации.
Такой подход демонстрирует глубокое понимание экосистемы push-уведомлений, а не только факт «пришло/не пришло».
Вопрос 14. Когда и как приложение должно запрашивать разрешение на отправку push-уведомлений?
Таймкод: 00:29:31
Ответ собеседника: неправильный. Сообщил, что разрешения отдельно не запрашивались и пуши по умолчанию были включены, не учитывая стандартные механизмы запросов прав.
Правильный ответ:
Корректное понимание механики разрешений на push-уведомления критично для тестирования. Поведение различается между платформами (iOS/Android) и эволюционирует с версиями ОС, поэтому важно знать базовые принципы и типичные практики.
Ключевые моменты:
- iOS: явный запрос разрешения обязателен
-
На iOS:
- Приложение не имеет права показывать push-уведомления, пока пользователь явно не дал согласие.
- Разработчик вызывает системный диалог через API (UNUserNotificationCenter).
- Пользователь видит нативный системный prompt:
- «Приложение "X" хочет отправлять вам уведомления».
- Варианты: Разрешить / Не разрешать.
-
Типичный UX-подход (best practice):
- Не показывать системный диалог сразу при первом запуске «в лоб».
- Сначала показать in-app pre-prompt:
- объяснить ценность уведомлений: «Будем присылать алерты о сделках, исполнении ордеров, важных событиях по портфелю».
- если пользователь согласен → вызвать системный диалог;
- если отказался → не дергать системный диалог, чтобы не получить перманентный «deny».
-
Что тестировать:
- системный диалог появляется в корректный момент (не спамит, не срывает критичные сценарии);
- при выборе «Allow»:
- статус в настройках iOS → включен;
- приложение корректно регистрирует push token;
- при выборе «Don’t Allow»:
- приложение не получает токен;
- нет пушей;
- в интерфейсе:
- корректные подсказки, как включить уведомления через системные настройки.
- Android: различия по версиям и типам уведомлений
Исторически:
- До Android 13:
- Push-уведомления через FCM фактически работали без отдельного системного разрешения, если приложение было установлено.
- Пользователь мог отключить уведомления в настройках системы вручную.
- Начиная с Android 13 (Tiramisu):
- Введено явное runtime-разрешение POST_NOTIFICATIONS.
- Поведение стало ближе к iOS:
- при первом использовании уведомлений приложение запрашивает разрешение;
- пользователь может разрешить или запретить.
Что важно при тестировании на Android:
- Для Android 13+:
- Проверить:
- корректный запрос runtime-разрешения в подходящий момент;
- сценарии «разрешил/запретил»;
- корректную работу при последующих запусках (нет повторного спама диалогами);
- корректную реакцию UI, если уведомления запрещены.
- Проверить:
- Для более старых версий:
- Проверить:
- поведение включенных по умолчанию уведомлений;
- реакцию приложения на ручное отключение уведомлений в системных настройках.
- Проверить:
- Где и как проверять разрешения:
Проверка на уровне платформы:
- iOS:
- Настройки → Уведомления → Выбрать приложение:
- Включены ли уведомления;
- Типы (баннеры, звуки, бейджи).
- Настройки → Уведомления → Выбрать приложение:
- Android:
- Настройки приложения → Уведомления:
- Включены ли уведомления;
- Категории (notification channels) — финансовые алерты, маркетинг, сервисные события.
- Настройки приложения → Уведомления:
Тестовые сценарии:
- Установить приложение → запустить:
- убедиться, что:
- iOS: нет скрытого обхода — пуши не приходят без разрешения;
- Android 13+: корректно запрашивается разрешение.
- убедиться, что:
- Изменить настройки уведомлений в системе:
- отключить → убедиться, что пуши не приходят;
- включить обратно → пуши снова приходят.
- Проверить in-app настройки:
- переключение категорий уведомлений внутри приложения:
- влияет на факт и типы приходящих push-уведомлений;
- корректно синхронизируется с backend (подписки/темы).
- переключение категорий уведомлений внутри приложения:
- Взаимодействие с backend и токенами
При выдаче разрешения:
- Приложение:
- запрашивает push token у FCM/APNs;
- отправляет его на backend, где хранится связка user_id ↔ device_id ↔ token.
- При отзыве разрешения или логауте:
- токен должен быть обновлен/удален;
- тестировщик проверяет корректность этого поведения.
Пример упрощенного backend-кода на Go:
type PushToken struct {
UserID string `json:"user_id"`
DeviceID string `json:"device_id"`
Token string `json:"token"`
Platform string `json:"platform"` // "ios" / "android"
UpdatedAt time.Time `json:"updated_at"`
}
func RegisterPushToken(w http.ResponseWriter, r *http.Request) {
var t PushToken
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
// upsert токена в БД
// ...
w.WriteHeader(http.StatusNoContent)
}
SQL-проверка корректной регистрации:
SELECT user_id, device_id, token, platform, updated_at
FROM push_tokens
WHERE user_id = 'test_user_123';
Что должен проверить тестировщик:
- токен создается/обновляется при включении уведомлений;
- токен удаляется/обнуляется при логауте/отказе от уведомлений (если так задумано);
- нет «мертвых» токенов, которые могут вести к невалидным попыткам отправки.
- Типичные ошибки, которые нужно уметь замечать:
- Пуши работают только потому, что на тестовом устройстве уже давно сохранен токен, а сценарий первого запуска/первого запроса прав не протестирован.
- Отсутствует корректный UX вокруг разрешений:
- нет объяснения пользователю, зачем нужны уведомления;
- приложение не обрабатывает отказ (молча ведет себя так, как будто пуши включены).
- Некорректная логика:
- пуши продолжают отправляться на токены пользователей, которые отозвали разрешения или удалили приложение.
- Отсутствие кросс-платформенной консистентности:
- разные тексты, разные сценарии, отсутствуют единые принципы безопасности и приватности.
Итоговая позиция:
- Зрелый ответ должен показывать:
- понимание различий iOS/Android в механизме разрешений;
- знание, что без явного согласия (особенно iOS, Android 13+) пуши работать не должны;
- умение тестировать сценарии:
- первый запрос;
- повторный запуск;
- смена настроек в системе;
- включение/отключение внутри приложения;
- связь с регистрацией токенов на backend и корректной маршрутизацией уведомлений.
Ответ вида «пуши просто по умолчанию включены» без понимания механики — это красный флаг, которого нужно избегать.
Вопрос 15. Что такое диплинки и как их правильно применять и тестировать?
Таймкод: 00:31:35
Ответ собеседника: неполный. Воспринимает диплинк как ссылку на конкретный экран приложения; использовал готовые ссылки от разработчиков и переходы через чат/мок в снифере. Не знает других способов вызова диплинков и технических деталей работы.
Правильный ответ:
Диплинки — ключевой механизм навигации и интеграции мобильного приложения с внешним миром: пуш-уведомления, веб-сайт, письма, баннеры, партнёрские ссылки, маркетинговые кампании, сценарии «вернуться в нужный экран по клику». Глубокое понимание диплинков важно как для тестирования UX, так и для проверки безопасности и корректности навигации.
Виды диплинков и базовая теория:
-
Классические (custom scheme)
- Формат:
myapp://screen/portfolio?account_id=123 - Приложение регистрирует собственную схему (
myapp://). - Плюсы: просто.
- Минусы:
- не работают из браузера без доп. логики, конфликтуют между приложениями;
- не поддерживают fallback по умолчанию (если приложение не установлено).
- Формат:
-
HTTP/HTTPS диплинки
- Формат: обычный URL, например:
https://app.example.com/portfolio?account_id=123
- Используются:
- как обычные web-ссылки;
- могут перехватываться приложением.
- Базис для:
- Android App Links;
- iOS Universal Links.
- Формат: обычный URL, например:
-
Universal Links (iOS) и App Links (Android)
- Привязка домена к приложению:
- на стороне сервера размещается специальный файл с маппингом (
apple-app-site-association,assetlinks.json); - ОС доверяет, что данный домен связан с конкретным приложением.
- на стороне сервера размещается специальный файл с маппингом (
- Если приложение установлено:
- ссылка открывается сразу в приложении;
- Если не установлено:
- открывается в браузере (fallback).
- Это стандарт для зрелых продуктов.
- Привязка домена к приложению:
Ключевые сценарии использования диплинков:
- Переходы из push-уведомлений:
- клик по пушу ведет на:
- экран сделки;
- детали инструмента;
- чат с менеджером;
- конкретную акцию/кампанию.
- клик по пушу ведет на:
- Переходы из email/SMS/баннеров:
- восстановление доступа;
- подтверждение почты/телефона;
- спецпредложения.
- Интеграции с вебом и партнёрами:
- промо-ссылки, UTM-метки, трекинг кампаний.
- Внутренние переходы:
- из webview в нативные экраны;
- из чата/сообщений/FAQ — по ссылке сразу к нужному действию.
Что и как нужно тестировать (структурно):
-
Корректность навигации:
- Для каждого диплинка:
- открывает ли он нужный экран:
- когда приложение:
- не запущено (cold start);
- в фоне;
- активно.
- когда приложение:
- правильно ли обрабатываются параметры:
order_id,ticket,account_id,campaign_id.
- открывает ли он нужный экран:
- Если данные некорректны:
- показывается ли внятная ошибка/заглушка;
- нет ли крашей.
- Для каждого диплинка:
-
Состояние авторизации:
- Критический момент, особенно для финансовых приложений.
Сценарии:
- Пользователь не авторизован:
- по диплинку на защищенный экран:
- должен попасть на экран логина;
- после успешного логина — редирект на целевой экран (deep link должен быть «пронесён»).
- по диплинку на защищенный экран:
- Пользователь авторизован:
- переход сразу на целевой экран без лишних шагов.
- Тестировать:
- диплинк на публичный контент (например, промо-страница) — должен открываться без авторизации;
- диплинк на чувствительные данные — только через авторизованную сессию.
-
Обработка невалидных/устаревших диплинков:
- Нельзя допускать:
- пустой экран,
- краш,
- некорректные данные.
- Тест:
- диплинк с несуществующим
order_id; - диплинк с битым форматом параметров;
- диплинк на старую фичу.
- диплинк с несуществующим
- Ожидаемое поведение:
- понятное сообщение;
- безопасный fallback (например, на главный экран или список сущностей).
- Нельзя допускать:
-
Интеграция с пушами и маркетингом:
- Проверить, что:
- payload пуша содержит корректный диплинк/данные для навигации;
- клик из пуша:
- открывает нужный экран;
- учитывает состояние авторизации;
- корректен на всех поддерживаемых платформах.
- Для маркетинговых кампаний:
- диплинк поддерживает UTM-метки / campaign_id;
- не ломает навигацию из-за дополнительных параметров.
- Проверить, что:
-
Безопасность диплинков:
- Нельзя:
- передавать в открытом виде чувствительные данные:
- полный номер карты, пароли, токены, паспорт.
- передавать в открытом виде чувствительные данные:
- Нельзя:
- позволять через диплинк выполнять опасные действия без подтверждения:
- автоподтверждение операции;
- изменение настроек;
- перевод средств.
- позволять через диплинк выполнять опасные действия без подтверждения:
Тестировщик должен проверять:
- что диплинки:
- требуют авторизацию там, где положено;
- не выполняют необратимых действий автоматически;
- безопасно обрабатывают неожиданные входные данные.
- Нельзя:
Практические способы тестирования диплинков:
-
Ручной запуск:
-
Через adb (Android):
adb shell am start -W -a android.intent.action.VIEW -d "myapp://portfolio?account_id=123" -
Через терминал/браузер/Notes на iOS:
- ввести
myapp://...илиhttps://app.example.com/...и перейти.
- ввести
-
-
Через сниферы/моки:
- Можно подменять URL-ы в Fiddler/Charles,
- но важно уметь запускать диплинки напрямую, без зависимости от заранее вшитых ссылок.
-
Автотесты:
- UI-тесты (Espresso/XCUITest/Appium/Detox), которые:
- запускают приложение по диплинку;
- проверяют правильность открытого экрана.
- UI-тесты (Espresso/XCUITest/Appium/Detox), которые:
Пример backend-логики на Go, связанной с диплинками:
- Генерация безопасной ссылки для подтверждения действия:
func GenerateConfirmLink(userID, actionID string) string {
token := signAction(userID, actionID) // HMAC/JWT
return "https://app.example.com/confirm?action_id=" + actionID + "&token=" + token
}
Тестировщик должен проверить:
- что без валидного token действие не выполняется;
- диплинк ведет:
- в приложение (если установлено),
- в браузер с корректным UX (если нет приложения).
Итоговая позиция для интервью:
Сильный ответ должен показать, что ты:
- понимаешь:
- виды диплинков (custom scheme, universal/app links);
- связь диплинков с пушами, письмами, вебом;
- влияние авторизации и безопасности;
- умеешь тестировать:
- навигацию из разных состояний приложения;
- обработку параметров;
- ошибки и невалидные ссылки;
- поведение при отсутствии приложения (fallback);
- используешь:
- прямой вызов диплинков (adb, URL-схемы),
- сниферы/моки — как дополнение, а не единственный способ.
Такой подход демонстрирует не только пользовательское понимание диплинков, но и техническое и системное видение их роли в архитектуре продукта.
Вопрос 16. Что должно происходить при использовании некорректной дипссылки и как правильно обрабатывать такие случаи?
Таймкод: 00:34:23
Ответ собеседника: неполный. Говорит, что при неверной дипссылке открывался чёрный экран, и это не считалось багом; далее признает, что корректнее показывать понятное сообщение об ошибке, а не пустой экран.
Правильный ответ:
Некорректная обработка диплинков — частый источник плохого UX и скрытых дефектов. Черный экран, бесконечный лоадер или краш при неверной дипссылке — это всегда дефект продукта, а не норма. Правильный подход: диплинки должны быть безопасны, устойчивы к ошибкам и предсказуемы для пользователя.
Ключевые принципы обработки некорректных диплинков:
-
Никогда не оставлять пользователя с «черным экраном»
- Недопустимые варианты поведения:
- черный экран без элементов управления;
- пустой экран;
- бесконечный спиннер без объяснения;
- краш приложения.
- Почему это баг:
- пользователь не понимает, что произошло;
- теряется доверие, особенно в банковских/инвестиционных продуктах;
- поведение не даёт возможности продолжить работу.
- Недопустимые варианты поведения:
-
Безопасный и понятный fallback При некорректной, устаревшей или частично битой дипссылке приложение должно:
- Выполнить валидацию параметров диплинка:
- обязательные параметры присутствуют;
- формат корректный (числа, UUID, enum-значения и т.п.);
- сущность существует (ордер, счёт, инструмент).
- Если данные невалидны:
- не делать «полупереход» в неизвестное состояние;
- показать:
- понятный экран с сообщением:
- «Ссылка недействительна или устарела»
- «Мы не нашли запрошенный объект»
- и/или перенаправить:
- на безопасный экран (главный, список счетов/портфелей, раздел помощи).
- понятный экран с сообщением:
- Важно:
- не выполнять автоматически чувствительные действия (подтверждение операции и пр.);
- не раскрывать лишнюю внутреннюю информацию в ошибке.
- Выполнить валидацию параметров диплинка:
-
Обработка разных типов проблем с диплинками:
Основные категории:
-
Некорректная схема или путь:
myapp://unknown_screenилиhttps://app.example.com/unknown.- Ожидаемое поведение:
- общий fallback-экран или редирект на главную + информирование.
-
Неверные или отсутствующие параметры:
myapp://order?order_id=илиorder_id=abcвместо числа.- Ожидаемое:
- валидация параметров;
- если ключевой параметр невалиден — показать ошибку, не пытаться открыть «полупустой» экран.
-
Не существующая сущность:
- диплинк на уже удаленный/несуществующий ордер/счет.
- Ожидаемое:
- запрос к backend;
- при 404 или аналогичном сценарии:
- сообщение «Объект не найден»;
- предложение вернуться в список или на главный экран.
-
-
Примеры корректного поведения (концептуально):
- Пользователь перешел по битой ссылке из письма:
- Приложение открывается → проверяет диплинк → не может найти сущность →
- показывает экран:
- «Ссылка недействительна или устарела»,
- кнопка «На главный экран».
- показывает экран:
- Приложение открывается → проверяет диплинк → не может найти сущность →
- Партнер прислал диплинк с ошибкой в параметре кампании:
- Приложение:
- не падает,
- не висит в пустоте,
- показывает безопасный fallback без «ломаной» логики.
- Приложение:
- Пользователь перешел по битой ссылке из письма:
-
Что должен проверять при тестировании диплинков:
- Поведение при:
- полностью некорректном URL;
- корректном пути, но невалидных параметрах;
- корректном формате, но несуществующих идентификаторах.
- Варианты состояний:
- приложение закрыто;
- в фоне;
- активно.
- Наличие:
- дружелюбного текста ошибки;
- кнопок для продолжения работы;
- отсутствия крашей и зависаний.
- Поведение при:
-
Пример простой схемы обработки диплинков (псевдологика):
На уровне клиента:
- Распарсить диплинк.
- Проверить:
- что маршрут известен;
- что обязательные параметры есть и валидны.
- Если условие не выполняется:
- не открывать «полупустой» экран;
- показать fallback/ошибку.
На уровне backend (если диплинк ведет на сущность):
Пример: Go-обработчик, который корректно обрабатывает несуществующую сущность:
func GetOrder(w http.ResponseWriter, r *http.Request) {
orderID := r.URL.Query().Get("order_id")
if orderID == "" {
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(map[string]string{
"error": "invalid_request",
"message": "order_id is required",
})
return
}
order, err := loadOrder(orderID)
if err == sql.ErrNoRows {
w.WriteHeader(http.StatusNotFound)
_ = json.NewEncoder(w).Encode(map[string]string{
"error": "not_found",
"message": "Order not found",
})
return
} else if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(map[string]string{
"error": "internal_error",
"message": "Please try again later",
})
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(order)
}Клиент по такому ответу:
- не падает,
- показывает пользователю понятное сообщение,
- при диплинке на несуществующий
order_id— обрабатывает ситуацию корректно.
-
Как это сформулировать на интервью:
Сильная позиция:
- Некорректная дипссылка не должна ломать приложение.
- Обязательные требования:
- валидация параметров;
- устойчивость к ошибкам;
- понятный UX (сообщение об ошибке + безопасный маршрут);
- отсутствие черных экранов и крашей.
- При тестировании:
- целенаправленно проверяю некорректные и устаревшие диплинки;
- считаю пустой/черный экран и «тихий фейл» полноценными багами.
Такой подход показывает понимание и пользовательского опыта, и технической надежности, и безопасной обработки данных при работе с диплинками.
Вопрос 17. Опиши структуру дипссылки: из каких основных частей она состоит.
Таймкод: 00:35:49
Ответ собеседника: неправильный. Не смог описать структуру дипссылки, упомянул только JSON c полем ссылки и признал, что не обращал внимания на формат.
Правильный ответ:
Диплинк по сути — это URL со строго определенной структурой, который используется для навигации в приложение (или на веб), передачи параметров и, при необходимости, безопасной идентификации контекста. Понимание структуры дипссылки важно для тестирования корректной маршрутизации, безопасности и интеграций.
Базовая структура любой дипссылки:
В общем виде диплинк (как и обычный URL) можно разложить на части:
scheme://host/path?query#fragment
Ключевые элементы:
-
Схема (scheme)
- Определяет, кто обрабатывает ссылку.
- Варианты:
- Custom scheme:
myapp://- пример:
myapp://portfolio/123
- HTTP/HTTPS:
https://app.example.com/...- используется для Universal Links (iOS) и App Links (Android).
- Custom scheme:
- Что важно:
- custom-схема должна быть уникальной (чтобы не конфликтовать с другими приложениями);
- https-ссылки позволяют использовать единый формат для веба и приложения.
-
Хост (host)
- Для custom-схемы:
- может быть логическим пространством маршрутов:
myapp://trading/...myapp://auth/...
- иногда опускается, и сразу идет путь.
- может быть логическим пространством маршрутов:
- Для https-схем:
- домен приложения или сервиса:
app.example.comlink.example.com
- домен приложения или сервиса:
- Для custom-схемы:
-
Путь (path)
- Описывает, какой экран или сущность нужно открыть.
- Примеры:
myapp://portfoliomyapp://portfolio/123https://app.example.com/orders/567
- Логика:
- сегменты пути часто кодируют:
- тип экрана (portfolio, order, instrument, profile),
- идентификатор сущности.
- сегменты пути часто кодируют:
-
Параметры запроса (query parameters)
- Часть после
?. - Используются для передачи динамических данных.
- Формат:
?key=value&key2=value2
- Примеры:
myapp://order?order_id=123&source=pushhttps://app.example.com/instrument?ticker=AAPL&campaign_id=SPRING2025
- Для тестирования важно:
- знать, какие параметры обязательны;
- проверять валидацию типов и значений;
- отслеживать, как они влияют на навигацию и состояние.
- Часть после
-
Фрагмент (fragment, часть после #)
- В вебе используется для якорей/внутренней навигации.
- В мобильных диплинках применяется реже, но может использоваться как дополнительный контекст.
- Пример:
https://app.example.com/help#faq_deposits
- Если используется:
- нужно проверить, что приложение корректно обрабатывает этот фрагмент.
Примеры корректно структурированных диплинков:
- Custom scheme:
myapp://portfolio?account_id=123- scheme:
myapp - host: (отсутствует/логический)
- path:
/portfolio - query:
account_id=123
- scheme:
- Universal/App Link:
https://app.example.com/order?order_id=987&source=push- scheme:
https - host:
app.example.com - path:
/order - query:
order_id=987source=push
- scheme:
- Сущность в path:
https://app.example.com/instrument/AAPL?source=email- path:
/instrument/AAPL - query:
source=email
- path:
Требования к хорошему диплинку:
- Однозначная маршрутизация:
- по ссылке понятно, какой экран и в каком контексте должен открыться.
- Явные и документированные параметры:
- есть спецификация: какие параметры, какие типы, какие обязательны.
- Безопасность:
- не передавать в диплинке:
- пароли;
- полные номера карт;
- токены доступа;
- персональные данные в открытом виде.
- для критичных действий:
- использовать защищенные токены и серверную валидацию (не доверять только содержимому ссылки).
- не передавать в диплинке:
Пример серверной генерации безопасного диплинка на Go:
func GenerateSecureDeepLink(userID, orderID string) string {
// Генерируем подписанный токен, который проверяется на backend
token := sign(userID, orderID) // HMAC/JWT
return "https://app.example.com/order?order_id=" + orderID + "&token=" + token
}
При тестировании:
- Проверить:
- структура соответствует спецификации;
- обязательные параметры обрабатываются;
- при отсутствии/некорректности параметров:
- нет краша;
- показывается понятная ошибка или выполняется безопасный fallback;
- критичные действия не выполняются только на основании открытого параметра без серверной проверки.
Итого:
- Дипссылка — это не «просто JSON с полем link», а полноценный URL с четкой структурой.
- Глубокое понимание:
- scheme + host + path + query (+ fragment)
- позволяет:
- осознанно тестировать навигацию;
- находить баги в маршрутизации;
- проверять безопасность и устойчивость при некорректных/злоумышленных входных данных.
Вопрос 18. Есть ли у тебя практический опыт работы с iOS как пользователем или при тестировании?
Таймкод: 00:36:38
Ответ собеседника: правильный. Почти не пользовался iOS и фактически не тестировал на этой платформе, за исключением единичных эпизодов.
Правильный ответ:
Для такого вопроса важен не только факт наличия/отсутствия опыта, но и понимание ключевых особенностей iOS, влияющих на тестирование. Даже если основной практический опыт связан с Android, нужно уметь:
- осознанно переносить знания на iOS,
- понимать платформенные отличия,
- быстро адаптироваться под экосистему.
Ключевые моменты, которые ожидается знать и уметь использовать:
- Особенности iOS, важные для тестирования:
-
Модель разрешений:
- Четко выраженный opt-in:
- уведомления, геолокация, камера, микрофон, контакты, трекинг (ATT), фото и т.д.
- Тестирование:
- поведение при первом запросе;
- сценарии «Allow» / «Don’t Allow»;
- изменение разрешений через системные настройки;
- реакция приложения при отсутствии нужного разрешения (корректные подсказки, отсутствие крашей).
- Четко выраженный opt-in:
-
Push-уведомления:
- Нельзя отправлять пуши без явного согласия пользователя.
- Используется APNs, особенности формата payload, delivery.
- Проверка:
- корректный запрос разрешения;
- поведение пушей в состояниях foreground/background/killed;
- навигация по пушу (deeplink/universal link).
-
Universal Links:
- Предпочтительный механизм диплинков:
https://...-ссылки открывают приложение, если оно установлено, иначе — веб.
- Тестирование:
- корректная привязка домена (apple-app-site-association);
- переходы из писем, браузера, мессенджеров;
- fallback при отсутствии приложения.
- Предпочтительный механизм диплинков:
-
Навигация и UX:
- Стандартные паттерны:
- таб-бар внизу, навигационный бар, свайпы назад;
- Требование к нативности поведения:
- важно проверять, что приложение не ломает привычные для iOS паттерны, если это критично для продукта.
- Стандартные паттерны:
- Практические аспекты тестирования iOS:
Даже при небольшом опыте, ожидается понимание инструментов:
- Xcode + iOS Simulator:
- запуск сборок;
- просмотр логов приложения;
- тестирование диплинков:
- открытие
xcrun simctl openurl booted "scheme://...".
- открытие
- Чтение логов:
- использование Console.app или логов Xcode для анализа крашей и ошибок.
- Crash- и analytics SDK:
- Firebase, Sentry, AppMetrica, etc. — аналогично Android, но с учетом iOS-специфики.
- Ожидаемая позиция на интервью:
Если реального коммерческого опыта мало, сильный ответ должен быть примерно таким по смыслу:
- Осознанное признание:
- "Основной боевой опыт — на Android, на iOS работал ограниченно."
- Понимание отличий:
- упоминание разрешений, Universal Links, APNs, особенностей push-логики, UX-паттернов.
- Готовность быстро закрыть гэп:
- "Понимаю архитектуру клиент–серверного взаимодействия, работу пушей, диплинков, аналитики; инструменты iOS (Xcode, симуляторы, логи) для меня понятны концептуально, при наличии доступа к технике быстро доучусь и встрою эти практики."
Важно показать:
- что отсутствие широкой практики на iOS — вопрос текущих условий, а не принципиальной неспособности;
- что базовые концепции мобильной разработки и тестирования переносимы между платформами;
- что ты понимаешь, какие именно аспекты iOS нужно тестировать иначе, чем на Android (разрешения, universal links, политика безопасности, поведение фоновых задач и уведомлений).
Вопрос 19. Как протестировать экран с одним полем ввода и кнопкой, которая определяет, является ли введённое значение числом?
Таймкод: 00:37:06
Ответ собеседника: неполный. Предлагает позитивные кейсы с разными числами, рассуждает про длину поля и отсутствие спецификации, приводит единичные примеры (0, 9, число в кавычках), но не формирует системный набор тестов: нет покрытия границ, отрицательных и дробных значений, пробелов, спецсимволов, пустого ввода и т.п.
Правильный ответ:
Подход к этому вопросу должен показать умение:
- быстро прояснить требования (что именно считается «числом»),
- системно применить эквивалентное разбиение и анализ граничных значений,
- не уйти в бесконечную «несформулированную спецификацию», а предложить разумный набор проверок,
- думать как о фронте (валидация, UX), так и о бэкенде (реализация проверки).
Сначала — ключевые уточнения (устно/мысленно):
Перед проектированием тестов важно явно проговорить (или спросить):
- Что считается числом в контексте задачи:
- Только целые неотрицательные? (0, 1, 123)
- Целые со знаком? (-1, +5)
- Дробные? (1.5, 0.0, 3.14, 1,5)
- В какой нотации:
- точка или запятая как разделитель?
- экспоненциальная форма (1e10)?
- Какие ограничения:
- максимальная длина ввода?
- есть ли ограничения по диапазону?
- Какое ожидаемое поведение:
- при валидном числе: показать «Да, это число», подсветить зеленым и т.п.
- при невалидном: «Не число», подсветка, подсказка.
- Где выполняется проверка:
- только на клиенте (JS/мобильный код);
- на сервере (API);
- оба варианта.
Если на интервью это не уточнили — стоит проговорить предположения и протестировать несколько сценариев трактовки.
Далее — системный набор тестов (предположим, что число = любое валидное десятичное число с возможным знаком и точкой).
- Позитивные кейсы (валидные числа):
- Простейшие:
- "0"
- "5"
- "9"
- "10"
- Многозначные:
- "123456"
- Со знаком:
- "-1"
- "+7"
- Дробные:
- "0.5"
- "10.0"
- "-3.14"
- Экстремальные по длине/размеру:
- очень длинное число в рамках допустимых требований (например, 20+ символов, если нет ограничений).
- Допустимые пробелы (при условии, что тримим ввод):
- " 123"
- "123 "
- " 3.14 "
- Если по требованиям пробелы вокруг допускаются → после trim значение валидно.
Проверяем:
- Корректно ли UI/логика определяет их как числа.
- Нет ли падений при больших значениях (на фронте и на бэкенде).
- Негативные кейсы (не число):
- Пустой ввод:
- ""
- " "
- Буквы и алфавитно-цифровые:
- "a"
- "abc"
- "123a"
- "a123"
- Спецсимволы:
- "!"
- "12!"
- "#$%"
- Число в кавычках:
- "'123'"
- ""123""
- Смешанные:
- "1 2" (внутренний пробел)
- "12-3"
- "++1"
- "--1"
- Неверный формат дробей:
- "1.2.3"
- "."
- "-"
- "+."
- Локализация (если договорились, что только точка допустима):
- "1,5" — должно считаться невалидным, если запятая не поддерживается.
- Очень длинный мусор:
- строка, превышающая ограничения длины, с символами.
Проверяем:
- Корректно определяет как «не число»;
- Сообщение об ошибке понятное;
- Нет краша, нет зависаний.
- Граничные значения:
Даже для простой задачи важно показать мышление через границы.
- Минимальные:
- пустая строка;
- "0" — частый edge-case.
- Максимальные:
- строка длиной = max_length - 1, max_length, max_length + 1.
- Важно понять:
- ограничивает ли поле ввод (UI) или сервер/валидация возвращает ошибку.
- Пограничные форматы:
- "-0"
- "+0"
- ".0" или "0."
- решаем по требованиям, считать ли это валидным.
- UX и поведение:
- Когда проверка выполняется:
- по клику на кнопку;
- по потере фокуса;
- в реальном времени.
- Что происходит:
- при ошибке: подсказка рядом с полем, красная рамка, не падает страница.
- при успехе: понятный результат.
- Отсутствие side-effects:
- повторное нажатие на кнопку;
- очистка поля;
- быстрый ввод/удаление.
- Кросс-платформенные и технические моменты:
Если есть серверная проверка:
- Нужно протестировать согласованность фронта и бэкенда:
- фронт считает "1e3" числом, а backend — нет (или наоборот) — это баг требований/контракта.
- Использовать снифер (Fiddler/Charles) для проверки:
- какие данные реально отправляются;
- как backend отвечает.
Примеры:
Простой серверный обработчик проверки числа на Go:
func isNumberHandler(w http.ResponseWriter, r *http.Request) {
s := r.URL.Query().Get("value")
// Тримим пробелы
s = strings.TrimSpace(s)
if s == "" {
respond(w, false)
return
}
// Пробуем распарсить как float
if _, err := strconv.ParseFloat(s, 64); err != nil {
respond(w, false)
return
}
respond(w, true)
}
func respond(w http.ResponseWriter, ok bool) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"is_number": ok})
}
Кейс для тестировщика:
- Проверить все вышеописанные значения против API:
- "123" → is_number=true
- "abc" → false
- "1.2.3" → false
- " 3.14 " → true
- "" → false
SQL здесь не обязателен, так как задача не про хранение, но если бы сохраняли значения, нужно проверить:
- что невалидные данные не попадают в таблицу;
- что тип поля в БД (INT, NUMERIC, VARCHAR) соответствует логике.
- Как это компактно сформулировать на интервью:
Хороший устный ответ:
- Коротко уточнить, что считаем числом.
- Сказать:
- применяю эквивалентное разбиение и граничные значения;
- проверяю:
- валидные целые и дробные;
- знаки, пробелы, большие числа;
- буквы, спецсимволы, пустой ввод, смешанные строки;
- поведение UI и сообщений об ошибке;
- согласованность фронт-/бэкенд-валидации, если она есть.
- Не закапываться только в «нет спецификации», а показать, как из этого состояния вытащить четкий набор проверок.
Такой подход демонстрирует зрелое, структурированное тест-дизайн мышление, а не перебор случайных примеров.
Вопрос 20. Какие конкретные значения ты проверишь в поле, чтобы отличить числа от нечисловых данных, и какие результаты ожидаешь?
Таймкод: 00:45:57
Ответ собеседника: неполный. Приводит отдельные примеры (0, 9, "0", одиночная буква, пустой ввод, SQL-инъекции), частично рассуждает про сообщения об ошибках, но не даёт системного покрытия классов значений и ожидаемых результатов.
Правильный ответ:
Задача: есть одно поле ввода и кнопка, которая определяет, является ли введённая строка числом. Ожидается системный набор проверок, основанный на:
- эквивалентном разбиении;
- анализе граничных значений;
- проверке типичных и нетривиальных вариантов ввода;
- аккуратном учёте UX и безопасности.
Ниже — пример структурированного набора тестов (предположим, что число — это валидное десятичное число, допускающее знак и точку; если требования иные, часть кейсов переедет из «валидных» в «невалидные»).
- Базовые валидные значения
Проверяют «нормальные» числа без шума.
- "0" → число
- "5" → число
- "9" → число
- "10" → число
- "123456" → число
Ожидаемый результат:
- Флаг/сообщение: «Это число» (true / OK).
- Нет ошибок, нет крашей.
- Знак числа
Показывают, что логика корректно обрабатывает + и -.
- "-1" → число (если отрицательные допустимы)
- "+1" → число (если плюс допускается)
- "-0" → число (в зависимости от трактовки, обычно да)
- "++1", "--1", "+-1" → не число
Ожидаемый результат:
- Корректные варианты: «Это число».
- Двойные и смешанные знаки: «Не число».
- Дробные числа
Проверяют поддержку десятичной части.
- "0.0" → число
- "3.14" → число
- "-3.14" → число (если отрицательные допустимы)
- ".5" / "5." → зависят от требований:
- либо валидные,
- либо «Не число» — важно явно зафиксировать.
- "1.2.3" → не число
Ожидаемый результат:
- Валидные форматы по договору: «Это число».
- Множественные точки и странные комбинации: «Не число».
- Пробелы
Тестируют, выполняется ли trim() и как обрабатываются внутренние пробелы.
- "123" → число
- " 123" → если тримим, число.
- "123 " → если тримим, число.
- " 3.14 " → при trim, число.
- "1 2" → не число (внутренний пробел).
- " " (только пробелы) → не число.
Ожидаемый результат:
- Внешние пробелы — по best practice игнорируются.
- Внутренние пробелы — «Не число».
- Пустая/пробельная строка:
- «Не число» + понятное сообщение: «Введите значение».
- Буквы и смешанные значения
Проверяют отделение числового ввода от текстового.
- "a" → не число
- "abc" → не число
- "123a" → не число
- "a123" → не число
- "12a34" → не число
Ожидаемый результат:
- Всегда: «Не число».
- Без крашей и некорректных преобразований.
- Специальные символы
Проверяют устойчивость к мусору и потенциальным инъекциям.
- "!" → не число
- "@" → не число
- "1!" → не число
- "$123" → не число
- ""0"" / "'0'" (число в кавычках) → не число
- "()123" → не число
- "" → не число
- "1 OR 1=1" → не число
Ожидаемый результат:
- «Не число» + отсутствие краша/инъекций.
- На уровне бэкенда:
- строка обрабатывается безопасно;
- нет выполнения SQL/JS;
- фронт сообщает о некорректном значении.
- Пустой ввод и нулевые кейсы
- "" (пустая строка) → не число
- " " (пробелы) → не число
Ожидаемый результат:
- Явное сообщение:
- «Введите значение» или «Поле не может быть пустым».
- Никаких падений, повторная проверка работает.
- Экзотика / дополнительные форматы (по согласованию)
Если система потенциально может поддерживать расширенные форматы — тестируем явно.
- Экспоненциальная форма:
- "1e3", "1E3" → либо число, либо «Не число», в зависимости от требований.
- Локализация:
- "1,5" → либо число, если запятая разрешена;
- либо «Не число», если принимаем только точку.
Важно:
- Сначала явно зафиксировать, поддерживаем ли мы эти форматы.
- Если нет — всегда: «Не число».
- Граничные значения по длине
Проверяют поведение на очень длинных вводах.
Допустим, max длина = 20 символов (пример, надо спросить).
- "12345678901234567890" → число (если не выходим за лимит и парсер выдерживает).
- Строка длиной > max:
- UI должен либо ограничивать ввод,
- либо backend/валидация возвращает «Не число» / «Слишком длинное значение».
Ожидаемый результат:
- Без переполнений, паник, 500-ошибок.
- Прозрачное ограничение.
- Проверка согласованности фронта и бэка (если есть API)
Если кнопка дергает backend, тестируем связку:
Пример API на Go:
func isNumberHandler(w http.ResponseWriter, r *http.Request) {
input := strings.TrimSpace(r.URL.Query().Get("value"))
if input == "" {
respond(w, false)
return
}
if _, err := strconv.ParseFloat(input, 64); err != nil {
respond(w, false)
return
}
respond(w, true)
}
func respond(w http.ResponseWriter, ok bool) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]bool{"is_number": ok})
}
Тестировщик должен:
- проверить все основные классы значений:
- валидные →
is_number: true; - невалидные (буквы, кавычки, SQL-мусор, пустые) →
is_number: false;
- валидные →
- убедиться, что нигде не происходит:
- 500 без понятной причины;
- SQL/код-инъекция;
- отличающееся поведение фронта и бэка.
Вывод (как ответ на интервью):
- Сначала уточняю, что считается числом.
- Затем системно проверяю:
- простые числа;
- знаки;
- дробные;
- пробелы;
- буквы и смешанные строки;
- спецсимволы и потенциальные инъекции;
- пустой ввод и длину.
- Для каждого класса значений формулирую однозначное ожидание: «число» или «не число», плюс корректное сообщение об ошибке и отсутствие крашей.
Такой ответ демонстрирует техническое и методичное мышление, а не случайный подбор примеров.
Вопрос 21. Как протестировать поведение приложения при отсутствии или потере интернет-соединения при проверке числа в форме?
Таймкод: 00:51:16
Ответ собеседника: неполный. Предлагает отключить интернет перед нажатием кнопки, ожидает продолжение проверки после восстановления сети, упоминает лоадер и ошибку при долгом отсутствии сети, но не формулирует четких критериев таймаутов, не разделяет случаи «нет сети» и «очень медленная сеть», не описывает мгновенную реакцию на отсутствие соединения.
Правильный ответ:
Корректный ответ должен показать:
- понимание, где именно выполняется проверка (клиент или сервер),
- различие между «нет сети», «медленная сеть», «сетевые ошибки сервера»,
- ясные ожидания по UX: лоадеры, таймауты, сообщения, отсутствие «зависаний» и повторов «магическим образом».
Сначала зафиксируем архитектурный контекст.
- Ключевой уточняющий вопрос
- Проверка «это число или нет» реализована:
- только на клиенте (локальная валидация)?
- на сервере (через API)?
- комбинированно (клиент предварительно валидирует, затем отправляет на сервер для доп.проверки/логики)?
От этого зависит сценарий:
- Если валидация целиком на клиенте:
- отсутствие сети не должно вообще влиять на определение «число/не число».
- Если валидация завязана на backend:
- отсутствие/потеря сети — полноценный кейс, требующий внятного UX.
Ниже разберем оба подхода.
- Если проверка происходит на клиенте
В хорошо спроектированной системе:
- Проверка числа:
- выполняется локально (JS/мобильный код),
- результат не зависит от сети.
Тестовые сценарии:
- Отключить интернет полностью:
- ввести валидное число → нажать кнопку → ожидаем мгновенный локальный результат «Число».
- ввести нечисло → ожидаем «Не число».
- Медленный интернет / флаппинг сети:
- вообще не влияет на результат.
- Важно:
- отсутствие лишних лоадеров и ожиданий;
- никаких запросов «просто чтобы проверить, число это или нет».
Вывод:
- Если логика такая, UX не должен зависеть от сети.
- Любой «зависимый от сети» детект числа в этом случае — архитектурный запах.
- Если проверка происходит на сервере (через API)
Допустим, кнопка отправляет введенное значение на backend, который возвращает is_number = true/false. Тогда нужно системно протестировать:
Состояния сети:
- нет сети (offline);
- сеть отвалилась в момент запроса;
- очень медленная сеть (таймаут);
- сеть есть, но сервер недоступен (5xx, connection refused).
3.1. Нет сети до нажатия кнопки
Сценарий:
- Отключаем сеть (в режиме полета / через dev tools / через прокси).
- Вводим значение.
- Нажимаем кнопку проверки.
Ожидания:
- Мгновенная реакция:
- либо:
- локальная проверка статуса сети и сразу понятное сообщение:
- «Нет интернет-соединения. Проверьте подключение и повторите попытку.»
- без бессмысленного лоадера;
- локальная проверка статуса сети и сразу понятное сообщение:
- либо:
- быстрый fail запроса (ошибка соединения) с тем же сообщением.
- либо:
- Никаких:
- бесконечных спиннеров;
- подвисаний UI;
- «магических» автопроверок спустя минуты.
3.2. Потеря сети во время запроса
Сценарий:
- Включена сеть.
- Вводим значение → нажимаем кнопку → запрос уходит.
- Во время запроса отключаем сеть/дропаем соединение.
Ожидания:
- По истечении разумного таймаута:
- отображается сообщение о проблеме сети:
- «Не удалось проверить из-за проблем с соединением»;
- пользователь может:
- повторить попытку вручную после восстановления сети.
- отображается сообщение о проблеме сети:
- Не должно быть:
- автоматического «дожидания сети» и внезапного результата без действий пользователя;
- зависания UI.
3.3. Медленная сеть (таймаут)
Сценарий:
- Ограничиваем пропускную способность (например, через Charles/Fiddler/OS).
- Нажимаем кнопку.
Ожидания:
- Появляется лоадер (индикация ожидания).
- По истечении таймаута:
- запрос прерывается;
- показывается понятное сообщение:
- «Превышено время ожидания ответа. Попробуйте позже.»
- Важно:
- таймаут должен быть конечным и разумным (например, 5–15 секунд, но это часть спецификации/настройки);
- лоадер должен исчезать, UI — оставаться отзывчивым.
3.4. Серверная ошибка при наличии сети
Сценарий:
- Сеть есть.
- Backend возвращает:
- 500, 502, 503, 504.
Ожидания:
- Пользователь видит:
- сообщение о технической ошибке:
- «Сервис временно недоступен»;
- а не «это не число».
- сообщение о технической ошибке:
- Деталь:
- нельзя подменять сетевую/серверную ошибку на результат валидации.
- различаем:
- «введено не число» (корректная обработка),
- «не удалось проверить» (техническая ошибка).
- Комбинированный подход (рекомендованный)
Оптимальная архитектура:
- базовая проверка «число/не число» — локально;
- сервер используется для более сложной логики, логирования, бизнес-правил.
Тогда:
- При отсутствии сети:
- локальная валидация все равно работает;
- если нужно обратиться к серверу — отображается отдельное сообщение:
- но результат «это число» / «не число» не зависит от сети.
- Отличный вариант для UX и надежности.
- Инструменты и техника тестирования
- Отключение сети:
- режим полета;
- отключение Wi-Fi/моб.данных;
- dev tools (для веб);
- сетевые профили в Charles/mitmproxy/Fiddler.
- Эмуляция:
- высокой задержки (latency);
- потерь пакетов;
- обрыва соединения.
- Пример серверной реализации на Go + ожидаемое поведение
func IsNumberHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
value := r.URL.Query().Get("value")
select {
case <-ctx.Done():
// Таймаут / отмена — корректная техническая ошибка
http.Error(w, `{"error":"timeout"}`, http.StatusGatewayTimeout)
return
default:
value = strings.TrimSpace(value)
if value == "" {
respond(w, false)
return
}
if _, err := strconv.ParseFloat(value, 64); err != nil {
respond(w, false)
return
}
respond(w, true)
}
}
func respond(w http.ResponseWriter, ok bool) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]bool{"is_number": ok})
}
Тестировщик:
- при нормальной сети:
- получает корректный true/false.
- при отключенной/потерянной сети:
- видит обработанную ошибку сети/таймаута на клиенте, а не падение.
- фронт:
- отделяет сообщение «не число» от «не удалось проверить».
- Как компактно ответить на интервью
Сильный ответ в устной форме:
- Сначала уточняю, где живет логика проверки.
- Если проверка локальная — сеть не влияет, тестирую, что все работает offline.
- Если есть запрос к backend:
- проверяю:
- отсутствие сети до клика (мгновенное понятное сообщение);
- потерю сети во время запроса (таймаут + ошибка без зависаний);
- медленную сеть (адекватный лоадер и ограниченный таймаут);
- сетевые ошибки сервера (5xx) — отличаю их от «не число».
- ожидаю:
- никаких крэшей, вечных спиннеров или «магического» продолжения старого запроса;
- только явное повторение пользователем после восстановления связи.
- проверяю:
Такой подход демонстрирует понимание сетевой надежности, UX и корректного разделения технических и бизнес-результатов.
Вопрос 22. Что ещё нужно проверить в простом одностраничном приложении помимо логики формы проверки числа?
Таймкод: 00:55:18
Ответ собеседника: неполный. Упоминает установку/запуск/удаление приложения, сворачивание/разворачивание, негативные сценарии ввода и базовую защиту (SQL-инъекции). Не покрывает важные аспекты: устойчивость валидации, UX, локали, форматы, обработку ошибок, кроссплатформенность и пр.
Правильный ответ:
Даже если приложение выглядит “игрушечным” (одно поле + одна кнопка), зрелый подход требует проверить вокруг формы весь контекст качества: функциональность, устойчивость, UX, безопасность, производительность, платформенные особенности. Ниже — структурированный список, который можно адаптировать под веб, мобайл или десктоп.
- Функциональность формы (дополнение к уже обсужденной логике)
Не повторяя детально Вопросы 19–20:
- Подтвердить:
- корректную работу для всех классов входных данных:
- валидные числа (целые, знаковые, дробные — согласно требованиям),
- нечисловые строки,
- пробелы, смешанные символы, спецсимволы.
- корректную работу для всех классов входных данных:
- Проверить:
- единую и предсказуемую модель поведения (однотипное сообщение для всех невалидных значений или осмысленные категории);
- отсутствие различий в логике между UI и backend:
- фронт не должен говорить «число», если backend считает иначе (и наоборот).
- UX и удобство использования
Даже для простого экрана это критично.
Проверить:
- Понятность интерфейса:
- есть ли пояснение, что делает форма;
- понятные тексты полей и кнопки;
- понятные сообщения при нечисловом вводе:
- «Введите число» vs бессмысленное «Error».
- Мгновенность и предсказуемость реакции:
- есть ли визуальный отклик по нажатию на кнопку (анимация/состояние disabled/лоадер);
- нет ли задержек без индикации.
- Обработка повторных действий:
- многократные клики по кнопке;
- быстрый ввод/удаление;
- изменение значения после получения результата — корректно ли пересчитывается.
- Доступность (Accessibility, если веб/мобайл):
- фокус на поле ввода при открытии;
- работа с клавиатурой (Tab, Enter);
- корректная работа с экранными ридерами:
- поле и кнопка имеют осмысленные labels;
- сообщения об ошибках читаются.
- Валидация форматов, локали и раскладки
Даже в “простом” приложении это часто источник багов.
Проверить:
- Локали:
- как ведет себя ввод в разных языках/региональных настройках:
- разделитель дробной части (точка против запятой);
- цифры национальных алфавитов (арабские/индийские и т.п., если релевантно);
- нет ли зависимости логики от языка интерфейса.
- как ведет себя ввод в разных языках/региональных настройках:
- Раскладки:
- случайный ввод букв из другой раскладки (например, русская «О» вместо латинской «0»);
- Автозамены и автокоррекция (мобайл):
- отключают ли они корректность ввода чисел;
- нет ли неожиданной подстановки.
- Состояния приложения и жизненный цикл
Если это мобильное или SPA-приложение:
- Сворачивание/разворачивание:
- сохраняется ли введенное значение и результат;
- нет ли краша или сброса состояния без причины.
- Рестарт приложения:
- если требования предполагают:
- сохранять ли последнее введенное значение?
- стартовать ли с чистого экрана?
- если требования предполагают:
- Поворот экрана (мобайл):
- при смене ориентации:
- не теряются ли данные;
- корректно ли масштабируется интерфейс.
- при смене ориентации:
- Ошибки сети и взаимодействие с backend
Если логика проверки завязана на сервер (см. Вопрос 21):
Проверить:
- Нет сети:
- мгновенное понятное сообщение, отсутствие вечных лоадеров.
- Медленная сеть:
- ограниченный таймаут;
- информирование о проблеме;
- отсутствие “подвешенного” состояния.
- Ошибки сервера:
- 4xx/5xx:
- различаем «некорректный ввод» и «проблема сервера»;
- показываем корректные сообщения.
- 4xx/5xx:
- Повторные запросы:
- нет дублирующих запросов при спаме кнопкой или нестабильной сети;
- отсутствует неконтролируемый retry.
Пример упрощенного backend-контракта (Go):
// GET /is-number?value=...
// Ответ: { "is_number": true/false }
Тесты:
- Проверить согласованность:
- фронт показывает результат, соответствующий JSON-ответу;
- при ошибках сети/сервера фронт не подменяет это на «не число».
- Безопасность
Даже простая форма — точка входа.
Проверить:
- Обработка вредоносного ввода:
- строки вида:
- "1 OR 1=1"
- "' OR ''='"
- ""
- длинные последовательности символов.
- строки вида:
- Ожидания:
- не выполняется SQL/JS;
- backend возвращает контролируемый ответ;
- UI отображает безопасное сообщение об ошибке;
- данные экранируются/валидируются.
- Никаких:
- stack trace-ов в ответах;
- утечек деталей инфраструктуры;
- ошибок 500 без обработки.
SQL-пример негативного теста:
-- Нежелательно видеть в логах/запросах что-то вроде:
SELECT * FROM numbers WHERE value = '' OR ''='';
Если подобное возможно — баг в серверной части.
- Производительность и устойчивость
Даже для простой операции важно убедиться в отсутствии деградаций.
Проверить:
- Большое количество запросов за короткий период:
- приложение не падает;
- сервер не уходит в 500 от простого спама;
- Очень длинный ввод (до максимально допустимой длины и за её пределами):
- UI не зависает;
- backend:
- отфильтровывает/обрезает;
- не падает по памяти или времени.
- Кросс-браузерность / кросс-платформенность
Если это веб:
- Проверить:
- разные браузеры (Chrome, Firefox, Safari, Edge);
- десктоп/мобильные браузеры;
- различия в HTML5-вводе (
type="number"vstype="text"+ JS-валидация).
Если это мобильное нативное:
- Проверить:
- разные версии ОС;
- разные устройства и DPI;
- поведение экранной клавиатуры:
- показывается ли цифровая клавиатура, если ожидается число.
- Логи и наблюдаемость
Для зрелой системы:
- Проверить, что:
- чувствительные данные не логируются в открытом виде;
- ошибки валидации логируются корректно (на уровне события, а не stack trace на каждый чих);
- при необходимости есть минимальная телеметрия, по которой можно понять:
- долю ошибочных вводов;
- возможные злоупотребления.
Пример: аккуратный лог на Go:
log.Printf("is-number request: value_length=%d is_number=%v", len(input), isNumber)
Без вывода полного пользовательского ввода.
- Итоговая формулировка для интервью
Краткий сильный ответ:
- Помимо проверки самой логики “число / не число”, я проверю:
- UX: понятные сообщения, поведение при ошибках, доступность.
- Разные форматы ввода: пробелы, знаки, дробные, локали, спецсимволы.
- Состояния приложения: сворачивание, перезапуск, поворот, сохранность данных.
- Сетевые сценарии (если есть backend): нет сети, таймауты, 4xx/5xx — без подвисаний и с корректными сообщениями.
- Безопасность: устойчивость к мусорному вводу и инъекциям, отсутствие утечек и крэшей.
- Кросс-браузерность / кросс-платформенность и поведение клавиатуры/элементов ввода.
- Логирование и отсутствие лишней чувствительной информации в логах.
Такой подход демонстрирует взгляд на приложение как на продукт целиком, а не на одну условную функцию.
Вопрос 23. Есть ли планы по внедрению новых технологий или инструментов для упрощения тестирования в команде?
Таймкод: 00:57:38
Ответ собеседника: правильный. Интересуется планами по новым технологиям; в ответ слышит, что кардинальных изменений не планируется, акцент на развитии существующей автоматизации: боты-уведомители, авто-создание задач, вспомогательные скрипты.
Правильный ответ:
На такой вопрос важно показать понимание, что «внедрение новых технологий» — не самоцель. Главный фокус — системное повышение эффективности и надежности процесса тестирования: сокращение ручной рутины, ускорение обратной связи, повышение стабильности пайплайна и прозрачности качества.
Хороший ответ строится так:
- Осознанный подход к новым инструментам
- Не гнаться за хайпом.
- Оценивать:
- ROI (что реально сократит трудозатраты и риски);
- интегрируемость с текущим стеком (CI/CD, трекер, репозитории, мониторинг);
- поддерживаемость командой (есть ли компетенции).
- «Новые технологии» часто = эволюция существующих решений:
- укрупнение сценариев автотестов;
- повышение стабильности и наблюдаемости;
- автоматизация операционных действий.
- Усиление существующей автоматизации
Если уже есть базовая инфраструктура (CI, автотесты, уведомляющие боты), логичный вектор:
- Расширять покрытие автоматизацией:
- критичные бизнес-флоу (регресс/смоук);
- контрактные тесты для API;
- интеграционные тесты между сервисами;
- проверку миграций БД.
- Повышать стабильность:
- детерминированные тестовые данные;
- изоляция окружений;
- борьба с flaky-тестами (ретраи с метрикой, quarantine, анализ причин).
Пример (Go): простой smoke-тест API, запускаемый в CI:
func TestHealthcheck(t *testing.T) {
resp, err := http.Get(os.Getenv("SERVICE_URL") + "/health")
if err != nil {
t.Fatalf("healthcheck request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status: %d", resp.StatusCode)
}
}
- Интеграция с инфраструктурой и ботами
Правильное направление — не только автотесты, но и «автоматизация вокруг»:
- Боты-уведомители:
- отправка результатов прогонов в Slack/Telegram/MS Teams:
- статус пайплайна;
- список свалившихся тестов с ссылками на логи/репорты;
- триггеры для ответственных команд.
- отправка результатов прогонов в Slack/Telegram/MS Teams:
- Автоматическое создание задач:
- при падении регрессии по конкретному модулю:
- создается задача с:
- логами,
- ссылкой на Allure/ReportPortal,
- ответственным по сервису.
- создается задача с:
- при падении регрессии по конкретному модулю:
- Автоматизация рутинных операций:
- генерация тестовых данных;
- ресет окружений;
- запуск локальных стендов;
- вспомогательные CLI/скрипты для тестировщиков и разработчиков.
Пример (Go): автосоздание задачи в трекере при падении теста (упрощенно, концептуально):
type Issue struct {
Title string `json:"title"`
Description string `json:"description"`
Assignee string `json:"assignee"`
}
func createIssueOnFailure(testName, logsURL string) error {
issue := Issue{
Title: "[AUTO] Failed test: " + testName,
Description: "Test failed. Logs: " + logsURL,
Assignee: "team-backend",
}
body, _ := json.Marshal(issue)
req, _ := http.NewRequest("POST", "https://tracker.local/api/issues", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("issue creation failed with status %d", resp.StatusCode)
}
return nil
}
- Направления, которые логично развивать
Даже без «кардинальных революций» можно планировать:
- Контрактные тесты и API-first:
- валидация схем (OpenAPI/Protobuf);
- гарантии совместимости между сервисами.
- Observability-ориентированное тестирование:
- использование логов, метрик, трассировок;
- проверки SLO/SLA в рамках тестов.
- Аналитика и качество тестов:
- анализ flaky-тестов;
- покрытие критичных маршрутов;
- интеграция с Code Review (pre-merge проверки).
SQL-пример: мониторинг частоты падений тестов по историям прогонов:
SELECT test_name,
COUNT(*) AS failures,
COUNT(*) FILTER (WHERE status = 'failed')::float
/ COUNT(*) * 100 AS fail_rate_percent
FROM test_runs
GROUP BY test_name
HAVING COUNT(*) > 20
ORDER BY fail_rate_percent DESC;
Используя такие отчеты, команда может целенаправленно стабилизировать самые проблемные тесты.
- Как это озвучить на интервью
Сильный ответ:
- Поддерживаю идею, что не всегда нужны резкие технологические повороты.
- Фокус — на:
- развитии текущей автоматизации;
- лучшей интеграции с CI/CD;
- ботах и сервисах, которые снижают ручную нагрузку и ускоряют фидбек.
- При этом:
- открыт к использованию новых инструментов там, где они реально дают выигрыш:
- генерация тестовых данных;
- контрактное тестирование;
- улучшенная отчетность;
- observability и quality gate’ы.
- открыт к использованию новых инструментов там, где они реально дают выигрыш:
Такой ответ показывает зрелое, прагматичное отношение к инженерным практикам: не «давайте прикрутим модный фреймворк», а «давайте системно сделаем наш pipeline быстрее, прозрачнее и надежнее».
Вопрос 24. Как часто происходит ротация ролей и направлений внутри команды тестирования и кого она затрагивает?
Таймкод: 01:01:00
Ответ собеседника: правильный. Уточняет частоту смены направлений и влияние на новых сотрудников. Получает ответ: внутри команды роли ротируются каждую неделю для равномерного распределения задач, есть возможность переходить между командами примерно раз в полгода или раньше по договоренности.
Правильный ответ:
При обсуждении ротации важно показать понимание баланса между:
- стабильностью экспертизы по домену и компонентам,
- диверсификацией опыта,
- снижением bus factor,
- адаптацией новых сотрудников без потери качества.
Ключевые аспекты грамотной ротации:
- Цели ротации:
- Снижение рисков bus factor:
- критичные области продукта (платежи, инвестиции, безопасность, push-уведомления, диплинки, аналитика) не должны быть завязаны на одного человека.
- Рост кросс-функциональной экспертизы:
- участники команды понимают архитектуру продукта целиком:
- API, мобайл, веб, интеграции, инфраструктура.
- участники команды понимают архитектуру продукта целиком:
- Более справедливое распределение задач:
- рутинные или «грязные» задачи (регресс, смоук, ручные проверки, triage) равномерно распределяются;
- интересные задачи (R&D, автотесты, системный анализ) не концентрируются в одних руках.
- Возможность профессионального развития:
- переход между направлениями: мобильное тестирование, backend/API, нагрузка, security, аналитика.
- Операционная ротация внутри команды (еженедельная/регулярная):
Хорошая практика — легкая ротация ролей внутри одной команды:
- Примеры ролевых зон:
- ответственный за:
- смоук/регресс релиза;
- проверку конкретных фич;
- triage инцидентов;
- документацию тест-кейсов;
- автотесты и инфраструктуру.
- ответственный за:
- Еженедельная/спринтовая ротация:
- помогает:
- всем понимать жизненный цикл задач;
- не выгорать на одном типе активности;
- формировать взаимозаменяемость.
- помогает:
- Важно:
- ротация не должна ломать контекст:
- крупные сложные фичи нужно доводить до конца теми же людьми или с контролируемой передачей знаний.
- ротация не должна ломать контекст:
- Межкомандная ротация (раз в несколько месяцев):
Речь о переходах между доменами/продуктами:
- Например:
- из команды мобильного банкинга → в команду инвестиционного модуля;
- из frontend QA → в API/интеграционное направление;
- из функционального тестирования → в автоматизацию.
- Разумная частота:
- каждые 6–12 месяцев или по запросу человека и потребностям продукта.
- Условия:
- не в разгаре критичных релизов;
- с планом передачи знаний;
- с участием лида/менеджера, чтобы не проседало качество.
- Работа с новичками:
Ротация не должна «ломать» онбординг:
- Новичкам:
- на старте нужна зона стабильности:
- понятный домен;
- наставник;
- минимальный шум задач.
- на старте нужна зона стабильности:
- Грамотный подход:
- первые месяцы — фокус на одном направлении;
- затем постепенное расширение:
- соседние модули;
- участие в регрессе по другим зонам;
- позже — осознанная ротация.
- Важно:
- не бросать новичка каждую неделю на новый модуль без контекста;
- иметь документацию, тест-кейсы, спецификации, чтобы ротация не превращалась в хаос.
- Контроль качества при ротации:
Чтобы ротация не снижала качество, нужны опоры:
- Документация:
- спецификации,
- тест-кейсы,
- чек-листы регресса,
- схемы архитектуры.
- Автоматизация:
- стабильные автотесты по критичным сценариям;
- они служат «страховкой», когда люди меняются.
- Ответственные за домены:
- даже при ротациях есть люди, глубже знающие конкретные области:
- они помогают ревьюить тест-подход и результаты.
- даже при ротациях есть люди, глубже знающие конкретные области:
- Как правильно ответить на интервью:
Сильная позиция:
- Ротация полезна, когда:
- управляемая;
- прозрачная;
- поддержана документацией и автотестами;
- учитывает уровень и фазу развития специалиста.
- Еженедельная ротация задач внутри команды:
- ок, если речь про виды активности (triage, регресс, автотесты), а не постоянную смену домена.
- Межкомандная ротация раз в полгода+:
- помогает развивать экспертизу и уменьшать риски.
- Важно:
- чтобы при ротации не терялось чувство ownership и ответственности за качество;
- чтобы решения принимались осознанно, а не как хаотичная «чехарда ролей».
Такой ответ демонстрирует понимание влияния ротации на качество продукта, знания команды и устойчивость процессов, а не только «факт, что можно переходить между командами».
Вопрос 25. Как компания реагирует на критические инциденты: привлекают ли сотрудников в нерабочее время и насколько стабилен функционал?
Таймкод: 01:02:50
Ответ собеседника: правильный. Уточнил про вызов сотрудников ночью или из отпуска и компенсацию. Получил ответ, что таких ситуаций недавно не было, функционал стабилен и ночных экстренных работ не требовалось.
Правильный ответ:
При обсуждении реакции на критические инциденты важно показать понимание:
- как должна выглядеть зрелая система incident management;
- как связаны стабильность функционала, качество тестирования и необходимость ночных эскалаций;
- чем отличается нормальная эксплуатация от «постоянного героизма».
Ключевые элементы грамотного процесса:
- Профилактика вместо постоянных «пожаров»
Зрелая инженерная культура строится так, чтобы:
- минимизировать вероятность критических инцидентов за счет:
- строгих quality gate в CI/CD (юнит-тесты, интеграционные, e2e, статический анализ, линтеры);
- продуманных регрессионных наборов (автоматизированных и ручных) для критичных сценариев;
- канареечных релизов, blue-green, feature-флагов;
- продуманной схемы миграций БД;
- мониторинга и алертинга (latency, error rate, бизнес-метрики).
- в итоге:
- вызовы «поднимите кого-то ночью» становятся редким исключением, а не нормой.
Пример: проверка критичного сервиса перед релизом (Go):
func TestCriticalFlow(t *testing.T) {
// smoke / sanity для ключевого бизнес-флоу
// если это падает в CI — релиз блочится
}
- Формализованный процесс реагирования на инциденты
Даже если инциденты редки, для критичных систем (финансы, инвестиции, платежи) должен быть понятный процесс:
- SLA/приоритеты:
- P0/P1 — полная недоступность сервиса, критичные деньги/операции, утечка данных;
- P2/P3 — частичные деградации, некритичные функции.
- Для P0/P1:
- обычно:
- есть on-call (дежурные по графику);
- возможен вызов в нерабочее время;
- это:
- компенсируется (деньгами или отгулами),
- регламентировано, а не «по дружбе».
- обычно:
- Для менее критичных:
- инциденты обрабатываются в рабочее время:
- без дергания людей ночью.
- инциденты обрабатываются в рабочее время:
Если в реальности за длительный период не было ночных эскалаций — это хороший сигнал о:
- стабильности функционала;
- адекватной релизной политике;
- эффективном тестировании до продакшена.
- Роль тестирования и автотестов в снижении количества инцидентов
Важно связать стабильность продакшена с техническими практиками, а не только «нам повезло».
Обычно это:
- автоматизированные smoke и регресс-запуски перед релизами;
- постоянные nightly и pre-release прогоны;
- проверка контрактов между сервисами;
- тестирование сценариев деградации:
- падение зависимостей,
- таймауты,
- ошибки БД.
SQL-пример бизнес-мониторинга (косвенный контроль инцидентов):
SELECT
DATE_TRUNC('hour', created_at) AS ts,
COUNT(*) FILTER (WHERE status = 'success') AS ok_count,
COUNT(*) FILTER (WHERE status = 'failed') AS fail_count
FROM payments
WHERE created_at >= NOW() - INTERVAL '24 hours'
GROUP BY ts
ORDER BY ts;
Резкие аномалии (рост fail_count) могут триггерить алерт еще до массовых жалоб пользователей.
- Как отвечать на вопрос на интервью
Сильная позиция:
- Понимать, что:
- редкость ночных вызовов и отсутствие постоянных экстренных фиксов — признак зрелых процессов и качественного тестирования.
- При этом:
- признавать, что для реально критичных инцидентов должен существовать формальный on-call/incident-процесс:
- понятные критерии, когда будить людей;
- понятная компенсация;
- обязательный postmortem:
- анализ причин,
- исправление,
- улучшение тестов, мониторинга, логирования.
- признавать, что для реально критичных инцидентов должен существовать формальный on-call/incident-процесс:
Формулировка:
- Корректно, когда:
- компания не живет в режиме перманентного 24/7 героизма;
- критические инциденты редки за счет качественной инженерии;
- но при этом есть готовая процедура быстрой реакции и компенсации, если действительно происходит что-то, что затрагивает деньги, безопасность или данные пользователей.
Такой ответ демонстрирует понимание связи между качеством системы, культурой on-call, тестированием и реальной эксплуатацией продукта.
Вопрос 26. Есть ли у тебя текущие офферы и какие предпочтения по типу работы и развитию?
Таймкод: 01:04:28
Ответ собеседника: правильный. Сообщает о наличии одного оффера с дедлайном и возможном втором; отмечает, что ключевой приоритет — развитие в автоматизации, а не выбор между вебом и мобилкой, с поэтапным переходом от смешанного формата (ручное + автотесты) к увеличению доли автоматизации.
Правильный ответ:
На такой вопрос важно ответить честно и одновременно показать зрелость в планировании карьеры и осознанный выбор стека/формата работы.
Ключевые моменты сильного ответа:
-
Прозрачность по текущим обязательствам:
- Если есть оффер с дедлайном:
- честно обозначить сроки, чтобы компания могла спланировать процесс:
- "У меня есть предложение с дедлайном до [дата]; готов оперативно проходить ваши этапы и предоставить ответ."
- честно обозначить сроки, чтобы компания могла спланировать процесс:
- Если есть другие процессы:
- кратко упомянуть, без лишних деталей:
- это нормально и воспринимается как стандартная практика рынка.
- кратко упомянуть, без лишних деталей:
- Если есть оффер с дедлайном:
-
Приоритеты по типу работы:
- Фокус не на «веб vs мобайл» как самоцели, а на:
- технологиях,
- качестве процессов,
- возможностях роста.
- Зрелая формулировка:
- "Мне важно работать там, где:
- есть осмысленная автоматизация,
- есть возможность влиять на качество архитектурно,
- есть живой CI/CD, нормальные пайплайны,
- есть культура инженерного подхода к тестированию."
- "Мне важно работать там, где:
- Фокус не на «веб vs мобайл» как самоцели, а на:
-
Развитие в автоматизации:
- Осознанный путь:
- старт в роли, где:
- можно сочетать ручное тестирование сложных сценариев,
- и постепенно брать ответственность за автотесты.
- по мере погружения:
- увеличивать долю автотестов:
- API-тесты (например, на Go),
- UI-тесты,
- контрактные тесты;
- участвовать в построении тестовой архитектуры:
- структура репозиториев,
- интеграция с CI/CD,
- тестовые данные, mock-и, контейнеризированные стенды.
- увеличивать долю автотестов:
- старт в роли, где:
- Важно подчеркнуть:
- "Я не хочу просто нажимать кнопки; мне важно строить воспроизводимый, автоматизированный процесс проверки качества."
- Осознанный путь:
-
Гибкость по домену и платформе:
- Хороший сигнал — готовность работать:
- с вебом, мобильными клиентами, backend/API, микросервисами;
- при условии, что это даёт возможность развивать инженерные навыки.
- Правильный акцент:
- "Готов подключаться к любому из направлений (web/mobile/backend-тестирование), если там есть здоровая техническая среда и возможность масштабировать автоматизацию."
- Хороший сигнал — готовность работать:
-
Что такой ответ транслирует компании:
- человек:
- открыт и честен по срокам и офферам;
- выбирает не «по случайности», а по качеству инженерной культуры;
- нацелен развиваться в сторону более технической роли:
- написание автотестов,
- понимание CI/CD,
- работа с кодом, логами, API,
- а не оставаться только в ручном регрессе.
- человек:
Это тот случай, когда краткий, честный и сфокусированный ответ выглядит значительно сильнее, чем попытка понравиться всем: он показывает осознанность, уважение к процессу компании и четкое понимание собственных профессиональных целей.
