РЕАЛЬНОЕ СОБЕСЕДОВАНИЕ Тестировщик DCloud - Middle 150+ тыс
Сегодня мы разберем живое техническое интервью, в котором кандидат по ручному тестированию демонстрирует уверенные теоретические знания и практический опыт работы с веб-сервисами, API, SQL и инструментами вроде Postman и DevTools. Беседа проходит в конструктивной, дружелюбной атмосфере: интервьюер глубоко проверяет понимание ключевых QA-концепций, а кандидат не только дает точные ответы, но и размышляет о качествах процессов, документации и своём дальнейшем развитии, в том числе в автоматизации.
Вопрос 1. Кратко опиши свой опыт: проекты, архитектура, роль в команде и используемые технологии.
Таймкод: 00:00:51
Ответ собеседника: правильный. Кратко рассказала о двух местах работы: стажировка и роль тестировщика. Участвовала в проекте с микросервисной архитектурой и CRM, тестировала фронт и API через Swagger и Postman, писала чек-листы и тест-кейсы. На текущем проекте для нефтегазового сектора тестирует дашборды и админку, использует PostgreSQL для простых запросов, Zephyr, Jira, Confluence, Figma, DevTools, работает по Scrum.
Правильный ответ:
Мой опыт включает участие в нескольких коммерческих проектах, где я работал с распределенными системами, микросервисной архитектурой и высоконагруженными сервисами, преимущественно на Go.
Ключевые аспекты моего опыта:
-
Архитектура:
- Проектирование и реализация микросервисов с четкими bounded contexts.
- Использование подходов DDD, CQRS (где оправдано), событийной архитектуры.
- Внимание к отказоустойчивости, масштабируемости, observability и устойчивости к частичным отказам.
- Декомпозиция монолита на микросервисы, унификация контрактов между сервисами (gRPC/REST), введение API Gateway и сервис-меша.
-
Backend на Go:
-
Разработка бизнес-критичных сервисов на Go:
- HTTP/REST и gRPC сервисы.
- Работа с конкуренцией: горутины, каналы, sync-примитивы, контекст, пул воркеров.
- Четкое управление жизненным циклом сервисов: graceful shutdown, контекст, таймауты и дедлайны.
-
Организация кода:
- Разделение на слои (transport, service, repository, domain).
- Использование dependency injection (в т.ч. через wire/fx или собственные подходы).
- Конфигурация через env/файлы, viper и пр.
-
Пример типичного HTTP-сервиса на Go:
package main
import (
"context"
"database/sql"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", os.Getenv("PG_DSN"))
if err != nil {
log.Fatalf("db connect error: %v", err)
}
defer db.Close()
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
if err := db.Ping(); err != nil {
http.Error(w, "db down", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
log.Println("server started on :8080")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen error: %v", err)
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("server shutdown error: %v", err)
}
log.Println("server stopped")
}
-
-
Хранение данных и интеграции:
-
PostgreSQL как основная БД:
- Проектирование схем, индексов, нормализация, оптимизация запросов.
- Использование транзакций, уровней изоляции, конкурентный доступ.
- Применение миграций (migrate, goose, liquibase).
-
Пример SQL-запроса с акцентом на производительность:
SELECT u.id,
u.email,
COUNT(o.id) AS orders_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created_at >= NOW() - INTERVAL '30 days'
GROUP BY u.id, u.email
HAVING COUNT(o.id) > 0
ORDER BY orders_count DESC
LIMIT 100; -
Кэширование:
- Использование Redis для горячих данных, rate limiting, сессий.
- Стратегии invalidation и консистентности.
-
-
Коммуникация между сервисами:
- gRPC для внутренних высоконагруженных вызовов.
- REST/JSON для внешних и публичных API.
- Асинхронные коммуникации:
- Kafka/RabbitMQ/NATS для событий и очередей.
- Идемпотентность, обработка повторов, dead-letter queues.
-
Observability и надежность:
- Логирование (structured logs, correlation-id/trace-id).
- Метрики (Prometheus): latency, error rate, saturation, бизнес-метрики.
- Трейсинг (OpenTelemetry, Jaeger/Tempo).
- Circuit breaker, retry, backoff, timeouts.
- Канарейка, blue-green, feature flags.
-
Безопасность и качество:
- Аутентификация и авторизация (JWT, OAuth2, RBAC).
- Валидация входных данных, защита от основных уязвимостей.
- Code review, unit-тесты, интеграционные и контрактные тесты.
- Использование Go tooling: go vet, golangci-lint, race detector, benchtests.
- CI/CD пайплайны: автоматические тесты, линтинг, деплой в Kubernetes/Docker.
-
Роль в командах:
- Участие в проектировании архитектуры и ключевых технических решений.
- Наставничество менее опытных разработчиков и участие в ревью.
- Взаимодействие с QA, аналитиками, DevOps, продуктовой командой.
- Инициирование и внедрение практик: observability, стандартизация логирования и API, повышение надежности и производительности.
Такое описание показывает не только список технологий, но и понимание архитектурных решений, компромиссов и ответственности за качество и надежность систем.
Вопрос 2. Есть ли в вашей команде формальный тест-план и как вы его используете на практике?
Таймкод: 00:06:11
Ответ собеседника: правильный. Есть тест-план, но его используют нечасто. В нем описаны виды тестирования, зоны ответственности, инструменты, критерии начала и завершения тестирования, метрики, версия документа. Тест-стратегию формирует лид и согласует ее с продакт-менеджером и владельцем продукта.
Правильный ответ:
Формальный тест-план и тест-стратегия являются частью общего инженерного процесса и должны быть не формальностью для аудита, а рабочим инструментом управления качеством.
Важно разделять понятия:
- Тест-стратегия — высокоуровневый документ: принципы, подходы, риски, уровни и типы тестирования, общие правила для продукта или домена.
- Тест-план — конкретизация под релиз, фичу или проект: что именно тестируем, в каких границах, какими силами, какими инструментами и в какие сроки.
Ключевые элементы рабочего тест-плана:
-
Область и цели:
- Что покрываем тестированием: конкретные сервисы, модули, интеграции.
- Явно зафиксированная зона ответственности: что входит и что сознательно не тестируется (out of scope), чтобы избежать ложных ожиданий.
-
Риски и приоритеты:
- Явная фиксация критичных областей:
- платёжные операции;
- авторизация/аутентификация;
- интеграции с внешними системами;
- операции с данными (целостность, миграции).
- На основе рисков строятся приоритеты тестов и глубина покрытия.
- Явная фиксация критичных областей:
-
Типы тестирования:
- Unit, integration, contract testing для микросервисов.
- API-тестирование (REST/gRPC), UI, e2e.
- Нагрузочное, стресс-тестирование для ключевых сервисов.
- Безопасность (basic security checks, auth flows).
- Регрессия: что автоматизировано, что остается в ручном виде.
-
Ответственность и взаимодействие:
- Кто отвечает за:
- написание автотестов (backend, QA automation);
- ручное тестирование критических сценариев;
- поддержку тестовых данных и сред;
- принятие решения о готовности к релизу.
- Важный момент: тест-план — не только зона QA, это общий артефакт команды разработки.
- Например, backend-разработчики отвечают за unit и integration тесты своих сервисов.
- QA — за e2e сценарии, exploratory testing и проверку рисков.
- Кто отвечает за:
-
Инструменты:
- Для API: Postman, Newman, Swagger/OpenAPI, автотесты на Go.
- Для UI: автотесты (Playwright/Cypress/Selenium), ручные чек-листы.
- Для backend-сервисов:
- Тестовые окружения в Kubernetes/Docker.
- Использование отдельных тестовых БД (PostgreSQL), seed-данных и миграций.
- Трекеры: Jira/YouTrack для задач и дефектов, Confluence/Notion для документации, Zephyr/TestRail для тест-кейсов.
-
Критерии начала и окончания тестирования:
- Start:
- Собрана и задеплоена тестовая версия.
- Есть спецификация/acceptance-критерии.
- Описание API и контрактов актуально (OpenAPI/gRPC proto).
- Stop:
- Критические и блокирующие дефекты устранены.
- Пройдены обязательные чек-листы и автотесты.
- Достигнуты целевые метрики качества (например, 0 blocker, 0 critical, покрытие ключевых сценариев).
- Start:
-
Метрики:
- Количество дефектов по severity.
- Покрытие автотестами ключевых сценариев.
- Среднее время нахождения и исправления дефектов.
- Регрессионные дефекты после релизов.
Как этим реально пользоваться в зрелой команде:
- Тест-план не должен быть статичным PDF, который никто не читает.
- Он живет вместе с продуктом:
- создается/обновляется в Confluence/репозитории;
- ссылается на конкретные коллекции, пайплайны, тестовые сценарии;
- используется при планировании релиза и go/no-go решениях.
- Для микросервисной архитектуры:
- Для каждого сервиса или домена есть минимальная "матрица качества":
- обязательные виды тестов;
- требования к логированию, метрикам, алертингу;
- требования к backward compatibility API.
- Это и есть практическая реализация тест-стратегии.
- Для каждого сервиса или домена есть минимальная "матрица качества":
Пример: как связать Go-сервис с тест-подходом
// Условный handler с явной обработкой ошибок и валидацией.
// Тест-план будет включать unit-тесты для валидации, интеграционные тесты для работы с БД
// и контрактные тесты для /users эндпоинта.
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
if err := req.Validate(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user, err := h.userService.CreateUser(ctx, req)
if err != nil {
// Логирование + корректный статус
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(user)
}
Для такого эндпоинта в тест-плане фиксируются:
- позитивные и негативные сценарии;
- граничные значения;
- контракт (schema) ответа;
- поведение при ошибках;
- кросс-сервисные эффекты (создание записей в БД, события в шину).
Таким образом, формальный тест-план и тест-стратегия — это не разовый документ, а системный инструмент, который связывает архитектуру, код, автотесты, ручное тестирование и процесс релизов в единый, предсказуемый процесс обеспечения качества.
Вопрос 3. Как в проектах используется Kafka и для каких задач она подходит?
Таймкод: 00:07:12
Ответ собеседника: неполный. Привела пример использования Kafka для асинхронного взаимодействия микросервисов: один сервис отправляет данные в топик, другой позже их читает. Отметила, что тогда не вникала глубоко в детали.
Правильный ответ:
Kafka чаще всего применяется как центральный компонент событийной и микросервисной архитектуры для:
- асинхронного взаимодействия сервисов;
- интеграции между доменами;
- обработки потоков данных в реальном времени;
- построения отказоустойчивых и масштабируемых пайплайнов.
Основные сценарии использования:
-
Асинхронное взаимодействие микросервисов:
- Вместо прямых HTTP/gRPC вызовов:
- Продюсер публикует событие (например,
UserCreated,OrderPaid) в топик. - Один или несколько консюмеров подписаны на этот топик и независимо обрабатывают событие.
- Продюсер публикует событие (например,
- Преимущества:
- Ослабленная связанность между сервисами.
- Устойчивость к временной недоступности получателя.
- Возможность легко добавлять новых потребителей без изменения продюсера.
- Вместо прямых HTTP/gRPC вызовов:
-
Event-driven архитектура:
- Kafka используется как "журнал событий" (event log).
- Сервисы не только реагируют на события, но и строят свои проекции данных:
- Например, сервис аналитики подписывается на события заказов и формирует агрегаты.
- Важный момент: события должны быть:
- неизменяемыми;
- семантически стабильными (backward compatible);
- описывать факт ("что произошло"), а не команду ("сделай").
-
Интеграция и data pipelines:
- Потоковая загрузка данных в DWH/аналитику.
- Репликация событий из транзакционных систем.
- Связка с Kafka Connect и Kafka Streams/Flink для трансформации и обогащения данных.
-
Масштабирование и отказоустойчивость:
- Топики разбиваются на партиции, что позволяет:
- масштабировать потребление по нескольким инстансам сервиса;
- обрабатывать большие объемы данных параллельно.
- Репликация партиций между брокерами повышает надежность.
- Топики разбиваются на партиции, что позволяет:
Ключевые концепции, которые важно понимать:
- Топик: логический канал сообщений.
- Партиция: упорядоченный лог внутри топика; порядок сообщений гарантирован только внутри партиции.
- Ключ сообщения:
- Определяет, в какую партицию попадет сообщение.
- Используется для сохранения порядка по ключу (например, все события по одному пользователю).
- Consumer Group:
- Сообщение из партиции доставляется только одному консюмеру внутри группы.
- Для горизонтального масштабирования консюмеров при сохранении семантики "один обработчик на партицию".
- Offset:
- Смещение в партиции; позволяет контролировать, какие сообщения уже обработаны.
- Delivery semantics:
- At-most-once, at-least-once, effectively-once (зависит от настройки продюсера, консюмера и логики приложения).
- На практике часто используют at-least-once + идемпотентность обработки.
Практика использования на стороне сервиса (Go-пример):
Ниже простая иллюстрация на Go (с использованием популярной библиотеки segmentio/kafka-go) для понимания организационных моментов.
Продюсер:
import (
"context"
"log"
"time"
"github.com/segmentio/kafka-go"
)
type UserCreatedEvent struct {
ID string `json:"id"`
Email string `json:"email"`
}
func publishUserCreated(ctx context.Context, event UserCreatedEvent) error {
w := &kafka.Writer{
Addr: kafka.TCP("kafka:9092"),
Topic: "user-created",
RequiredAcks: kafka.RequireAll,
Balancer: &kafka.Hash{}, // определяем партицию по ключу
}
defer w.Close()
payload, err := json.Marshal(event)
if err != nil {
return err
}
msg := kafka.Message{
Key: []byte(event.ID), // все события по одному пользователю в одной партиции
Value: payload,
Time: time.Now(),
}
if err := w.WriteMessages(ctx, msg); err != nil {
log.Printf("failed to write message: %v", err)
return err
}
return nil
}
Консюмер:
func consumeUserCreated(ctx context.Context) {
r := kafka.NewReader(kafka.ReaderConfig{
Brokers: []string{"kafka:9092"},
Topic: "user-created",
GroupID: "email-service", // consumer group
MinBytes: 1,
MaxBytes: 10e6,
})
defer r.Close()
for {
m, err := r.ReadMessage(ctx)
if err != nil {
if ctx.Err() != nil {
return // graceful shutdown
}
log.Printf("read error: %v", err)
continue
}
var event UserCreatedEvent
if err := json.Unmarshal(m.Value, &event); err != nil {
log.Printf("unmarshal error: %v", err)
// решение: отправить в DLQ, залогировать, пропустить и т.д.
continue
}
// идемпотентная обработка:
// - проверяем, не обрабатывали ли уже этот event.ID
// - записываем результат в БД транзакционно
if err := handleUserCreated(ctx, event); err != nil {
log.Printf("handle error: %v", err)
// опционально: retry, DLQ
continue
}
}
}
Важные инженерные моменты при работе с Kafka:
- Идемпотентность обработки:
- At-least-once означает, что сообщения могут прийти повторно.
- Нужны ключи, уникальные идентификаторы и проверки на стороне БД.
- Обработка ошибок:
- Dead Letter Queue (DLQ) для ядовитых сообщений.
- Мониторинг rate ошибок.
- Схемы сообщений:
- Явное конструирование контрактов (JSON Schema, Protobuf, Avro).
- Backward/forward compatibility при изменении полей.
- Observability:
- Метрики по лагу консюмера, количеству сообщений, ошибкам.
- Логирование ключевых событий обработки.
Таким образом, корректное использование Kafka — это не только "отправить/прочитать сообщение", а продуманная архитектура событий, контрактов, гарантий доставки и идемпотентной обработки, чтобы система была масштабируемой, надежной и предсказуемой.
Вопрос 4. На каком этапе жизненного цикла продукта оптимально подключать тестирование?
Таймкод: 00:08:31
Ответ собеседника: неполный. Описывает участие после работы аналитиков: проверка требований и участие в грумингах. Идея раннего вовлечения есть, но не раскрыта как принцип полного сопровождения жизненного цикла.
Правильный ответ:
Оптимальный подход — вовлечение тестирования на максимально ранних этапах жизненного цикла продукта и сопровождение до production и постпродакшн-аналитики. Это принцип:
- "Shift Left" — смещение активности по качеству влево, к этапам идеи, анализа и дизайна.
- "Shift Right" — дополнение контроля качества метриками, логами, нагрузочными и эксплуатационными аспектами уже в проде.
Ключевые этапы и роль тестирования:
-
Инициация и продуктовая идея:
- Подключение уже на этапе формирования гипотезы и бизнес-требований.
- Что делают специалисты по качеству:
- помогают выявить риски и критичные бизнес-сценарии;
- уточняют, какие метрики успеха фичи подлежат проверке;
- задают вопросы о граничных случаях, ограничениях, регуляторных требованиях, безопасности.
- Результат: бизнес-требования формируются более полными и проверяемыми.
-
Аналитика и формализация требований:
- Участие в обсуждении требований, user stories, acceptance criteria.
- Основные задачи:
- проверка требований на:
- полноту (нет ли отсутствующих сценариев);
- непротиворечивость;
- тестопригодность (можно ли однозначно проверить?);
- конкретику (избегание размытых формулировок).
- Формирование наборов будущих тестовых сценариев уже на этапе требований.
- проверка требований на:
- Если здесь подключаться системно — сильно снижается количество дефектов реализации.
-
Архитектура и дизайн решения:
- В идеальной команде специалисты по качеству участвуют в обсуждении архитектурных решений:
- интеграции с другими системами;
- использование очередей, брокеров, кешей, внешних API;
- механизмы логирования, трассировки, фича-флагов, graceful degradation.
- Почему это важно:
- многие проблемы с надежностью, наблюдаемостью и тестируемостью закладываются на этом этапе.
- Например:
- требование: "операция должна быть идемпотентной";
- требование: "должны быть метрики ошибок и latencies для каждого ключевого эндпоинта";
- договоренность: "каждый публичный API имеет контракт (OpenAPI/proto) и тестируемую схему".
- В идеальной команде специалисты по качеству участвуют в обсуждении архитектурных решений:
-
Этап разработки:
- Тестирование не ждет "готовый продукт":
- разработчики покрывают код unit и integration тестами;
- специалисты по качеству готовят тест-кейсы, данные, автотесты;
- проверяются контракты API, схемы сообщений (Kafka, очереди), миграции БД.
- Хорошая практика:
- тест-кейсы и автоматизация развиваются параллельно с фичей;
- PR-ревью включают проверку логируемости, обработку ошибок, валидацию входных данных.
Пример: проверка тестируемости Go-кода на этапе разработки.
// Хорошо тестируемый код: чистая бизнес-логика в отдельном слое,
// зависимости прокинуты интерфейсами.
type UserRepo interface {
Create(ctx context.Context, u User) error
Exists(ctx context.Context, email string) (bool, error)
}
type UserService struct {
repo UserRepo
}
func (s *UserService) Register(ctx context.Context, email string) error {
exists, err := s.repo.Exists(ctx, email)
if err != nil {
return fmt.Errorf("check exists: %w", err)
}
if exists {
return fmt.Errorf("user already exists")
}
return s.repo.Create(ctx, User{Email: email})
}При таком дизайне:
- легко писать unit-тесты с моками репозитория;
- тестируемость учитывается уже при проектировании.
- Тестирование не ждет "готовый продукт":
-
Этап интеграционного и системного тестирования:
- Подключение к:
- интеграции микросервисов;
- проверке очередей (Kafka), брокеров, асинхронных процессов;
- проверке схем БД, миграций, backward compatibility.
- Здесь используются:
- e2e тесты;
- контрактные тесты между сервисами;
- проверка нефункциональных требований (производительность, устойчивость, деградация).
Пример SQL-подхода:
- На этапе интеграции проверяется, что миграции не ломают существующие запросы:
-- Пример миграции, не ломающей старые запросы:
ALTER TABLE users
ADD COLUMN IF NOT EXISTS phone TEXT;
CREATE INDEX IF NOT EXISTS idx_users_email ON users (email);- И в тестах проверяется:
- доступность старых колонок;
- корректность новых индексов под частые запросы.
- Подключение к:
-
Pre-production / UAT:
- Проверка на окружении, максимально близком к боевому.
- Валидация:
- ключевых бизнес-сценариев;
- ролей и прав доступа;
- производительности под реальными нагрузками;
- поведения в случае отказов внешних сервисов.
- Решение go/no-go принимается на основе:
- статуса тестов;
- дефектов по severity;
- метрик качества.
-
Production и постпродакшн (Shift Right):
- Участие в контроле качества уже в бою:
- мониторинг метрик (ошибки, latency, бизнес-показатели);
- анализ логов, алертов, инцидентов;
- участие в post-mortem и улучшении архитектуры/проверок.
- Тестирование в проде:
- canary релизы;
- feature flags;
- A/B тесты;
- безопасные эксперименты на ограниченной аудитории.
- Участие в контроле качества уже в бою:
Итоговая идея:
- Идеальный ответ: тестирование должно начинаться не после разработки, а с момента появления идеи и первых требований, продолжаться на этапах дизайна, разработки, интеграции, релиза и эксплуатации.
- Цель: минимизировать стоимость ошибок, сделать систему проверяемой и наблюдаемой по умолчанию, а качество — встроенной характеристикой продукта, а не конечным этапом перед релизом.
Вопрос 5. В идеальной ситуации, на каком этапе жизненного цикла продукта нужно начинать участвовать в тестировании?
Таймкод: 00:08:31
Ответ собеседника: правильный. Описывает текущую практику включения после подготовки требований и участия в их уточнении. Затем формулирует идеальный подход: подключаться как можно раньше, ещё на этапе работы с ТЗ, чтобы находить противоречия и дефекты в документации до разработки, снижая стоимость исправлений.
Правильный ответ:
Идеальный ответ: участвовать в обеспечении качества нужно с самого начала жизненного цикла продукта — с момента появления идеи и первых формулировок требований — и продолжать это участие до и после выхода в прод.
Кратко по сути:
- Начинать стоит:
- на этапе формулирования бизнес-идеи и ТЗ;
- до детализации требований и начала разработки.
- Цель:
- найти и устранить логические противоречия, пропуски, неясности, нефункциональные требования (производительность, безопасность, интеграции) как можно раньше;
- заложить тестопригодность и наблюдаемость в архитектуру и дизайн.
Практически это выглядит так:
-
На этапе идеи и ТЗ:
- проверка, что формулировки конкретны и проверяемы;
- выявление критичных сценариев (платежи, безопасность, консистентность данных);
- фиксация критериев приемки и метрик успеха.
-
На этапе аналитики и проектирования:
- участие в ревью требований и API-контрактов;
- уточнение граничных условий, ошибок, ролей и прав;
- влияние на дизайн так, чтобы система была:
- тестируемой (слои, интерфейсы, контракты),
- наблюдаемой (логи, метрики, трассировка),
- устойчивой к сбоям.
-
На этапе разработки:
- параллельная подготовка тест-кейсов и автотестов;
- согласование форматов данных, схем БД, контрактов (OpenAPI/proto);
- ранняя проверка unit, integration, contract-тестами.
-
На этапах интеграции, pre-prod и prod:
- проверка сценариев end-to-end;
- оценка готовности к релизу по четким критериям;
- мониторинг в проде, участие в разборе инцидентов и улучшении системы.
Таким образом, правильный подход — это "качество по всей длине": тестирование как мышление и практика присутствует на всех фазах, а не только после написания кода.
Вопрос 6. В чем разница между подходами тестирования черного, белого и серого ящика?
Таймкод: 00:10:40
Ответ собеседника: правильный. Описала, что:
- черный ящик — тестирование без знания внутренней реализации;
- белый ящик — с доступом и пониманием кода;
- серый ящик — комбинированный подход с частичным знанием внутренней структуры.
Правильный ответ:
Подходы "черный", "белый" и "серый" ящик описывают то, насколько глубоко тестирующий опирается на внутреннее устройство системы при проектировании тестов.
Важно понимать не только определение, но и практическое применение в современных сервисах, включая backend на Go, микросервисы и базы данных.
Черный ящик (Black-box testing):
Суть:
- Тестировщик полностью исходит из внешнего поведения системы:
- входные данные → ожидаемый результат.
- Внутреннее устройство (архитектура, код, алгоритмы, структура БД) не учитываются или сознательно игнорируются.
Где применяется:
- Тестирование бизнес-функционала:
- API по спецификации (OpenAPI/Swagger/gRPC контракт).
- UI-тесты.
- E2E сценарии.
- Приемочное тестирование (acceptance/UAT).
- Регрессионное тестирование по зафиксированным сценариям.
Плюсы:
- Фокус на реальных пользовательских и бизнес-сценариях.
- Не завязан на конкретную реализацию: проще поддерживать при рефакторинге.
Минусы:
- Риск пропустить скрытые ветки логики и edge cases, если они не отражены в требованиях.
- Может быть менее эффективно по покрытию сложной логики.
Пример (Go + HTTP API, как тест черного ящика):
Мы знаем только контракт:
POST /api/v1/users
Body: { "email": "test@example.com" }
Response: 201 Created или 400 / 409
Тест (условно, black-box e2e):
- Передать валидный email → ожидать 201.
- Передать некорректный email → 400.
- Создать пользователя, затем повторно с тем же email → 409.
Тесты не обязаны знать, как внутри работает репозиторий или транзакции.
Белый ящик (White-box testing):
Суть:
- Тесты проектируются с учетом знания внутренней реализации:
- структуры кода;
- ветвлений;
- алгоритмов;
- схемы БД;
- точек интеграции.
- Цель — достичь максимального покрытия логики и проверить все значимые ветви исполнения.
Где применяется:
- Unit-тесты на Go:
- проверка отдельных функций, методов, структур.
- Тесты на уровень сервисов и репозиториев:
- обработка ошибок, таймаутов, ретраев.
- Анализ и покрытие критичных алгоритмов:
- расчеты, биллинг, безопасность, валидация.
Плюсы:
- Высокая точность и глубина проверки.
- Возможность проверить редкие и сложные ветви логики, которые трудно воспроизвести только через публичный интерфейс.
Минусы:
- Сильно завязаны на реализацию:
- рефакторинг может ломать тесты, даже если поведение не изменилось.
- Нельзя использовать как единственный источник уверенности: он не гарантирует корректность с точки зрения бизнеса.
Пример (Go unit-тест, тест белого ящика):
func TestRegister_UserAlreadyExists(t *testing.T) {
ctx := context.Background()
repo := &MockUserRepo{
ExistsFunc: func(ctx context.Context, email string) (bool, error) {
return true, nil
},
}
svc := &UserService{repo: repo}
err := svc.Register(ctx, "test@example.com")
if err == nil || err.Error() != "user already exists" {
t.Fatalf("expected 'user already exists', got %v", err)
}
}
Здесь тест знает:
- что есть
UserService; - что он вызывает
repo.Exists; - какие сообщения об ошибках возвращаются.
Серый ящик (Gray-box testing):
Суть:
- Комбинация подходов:
- Тестировщик знает внутреннюю архитектуру, схемы, контракты, потоки данных,
- но тесты исполняются преимущественно через внешние интерфейсы или полу-внешние (API, сообщения, интеграции).
- Используется знание "как внутри устроено", чтобы лучше проектировать сценарии, но не привязывать тесты жестко к каждой детали кода.
Где применяется:
- Интеграционные тесты:
- знаем, что сервис пишет в PostgreSQL или Kafka и читаем/проверяем последствия.
- Контрактные тесты между микросервисами:
- проверяем, что формат данных, поля, ошибки соответствуют договоренностям.
- Тестирование асинхронных процессов:
- знаем, что после POST в один сервис должно появиться сообщение в топике Kafka и запись в другой БД.
Плюсы:
- Баланс между реализмом и глубиной проверки.
- Позволяет целенаправленно тестировать критичные места (транзакции, очереди, кеши, миграции).
Минусы:
- Требует хорошего понимания системы.
- Есть риск "подстроить" тесты под текущую реализацию и не заметить изменение требований.
Пример серого ящика (SQL + сервис):
Сценарий:
- Мы знаем, что при создании заказа сервис:
- пишет запись в таблицу orders;
- публикует событие в Kafka.
Тест:
- Через API:
POST /api/v1/orders
Body: { "user_id": 1, "amount": 100 }
→ ожидаем 201 Created
- Проверяем в тестовом окружении БД:
SELECT status, amount
FROM orders
WHERE user_id = 1
ORDER BY created_at DESC
LIMIT 1;
Ожидаем: статус "created", amount = 100.
- При необходимости — проверяем, что в тестовом Kafka-топике появилось корректное событие (по схеме).
Это уже не чистый "черный ящик", потому что тест осознанно использует знание про структуру данных и архитектуру.
Как это применять в реальных проектах:
- Черный ящик:
- для бизнес-потоков, UI, публичных API, приемочного и регрессионного тестирования.
- Белый ящик:
- для unit-тестов, сложных алгоритмов, критичных к ошибкам мест.
- Серый ящик:
- для микросервисной архитектуры, интеграций, очередей, БД, контрактов и e2e через знание внутренней структуры.
Зрелый подход к качеству использует все три стратегии в комплексе:
- внешнюю корректность (black-box),
- внутреннюю надежность и покрытие логики (white-box),
- осознанную проверку интеграций и потоков данных (gray-box).
Вопрос 7. Что такое сквозное (end-to-end) тестирование?
Таймкод: 00:11:47
Ответ собеседника: правильный. Определила как проверку продукта от начала до конца через полный пользовательский сценарий с учетом взаимодействия с внешними интерфейсами.
Правильный ответ:
Сквозное (end-to-end, E2E) тестирование — это проверка реальных пользовательских и бизнес-сценариев «от входа до выхода» через всю систему целиком, включая:
- фронтенд;
- backend-сервисы;
- базы данных;
- очереди/брокеры сообщений;
- внешние интеграции (платежи, сторонние API и т.п.);
- инфраструктурные компоненты, влияющие на поведение.
Ключевая идея: подтвердить, что вся цепочка, как она реализована в проде, работает корректно, согласованно и удовлетворяет бизнес-требованиям.
Основные характеристики E2E-тестов:
- Ориентация на сценарий, а не на отдельный модуль:
- проверяется не функция/метод, а полный поток действий.
- Используются реальные или максимально близкие окружения:
- тот же тип БД;
- те же конфигурации сервисов;
- максимально приближенные настройки очередей, кешей, API Gateway и пр.
- Проверяется взаимодействие между компонентами:
- корректность контрактов API;
- целостность данных на всем пути;
- корректная работа асинхронных процессов.
Примеры типичных E2E-сценариев:
- Регистрация пользователя:
- пользователь заполняет форму → запрос уходит в backend → создается запись в БД → отправляется письмо → UI видит успешный результат.
- Оформление заказа:
- авторизация → выбор товара → создание заказа → оплата → изменение статуса → запись в БД → событие в очередь → обновление дашборда или уведомление.
Практический пример сквозного сценария (микросервисная архитектура):
Сценарий: "Создать заказ и убедиться, что он отразился в системе аналитики".
Последовательность:
- UI/клиент отправляет запрос:
POST /api/v1/orders
Authorization: Bearer <token>
Body: { "user_id": 123, "item_id": 456, "amount": 2 }
-
API-gateway проксирует запрос в сервис заказов.
-
Сервис заказов:
- валидирует данные;
- проверяет наличие товара (через сервис каталога);
- создает запись в PostgreSQL:
INSERT INTO orders (user_id, item_id, amount, status)
VALUES (123, 456, 2, 'created');- публикует событие в Kafka-топик
order-created.
-
Сервис аналитики:
- читает события
order-createdиз Kafka; - обновляет агрегаты или витрины в своей БД.
- читает события
-
E2E-тест проверяет:
- ответ API: 201 Created и корректный body;
- наличие записи в таблице orders с ожидаемым статусом;
- появление соответствующих данных в аналитической БД/витрине;
- корректное отображение данных в UI/отчетах (если это часть сценария).
Этот тест:
- не интересуется внутренними деталями реализации каждого метода,
- но подтверждает, что вся экосистема отрабатывает целевой бизнес-процесс.
Где E2E особенно важны:
- Критичные бизнес-процессы:
- платежи, биллинг;
- создание и выполнение заказов;
- права доступа и авторизация;
- операции, влияющие на деньги или юридически значимые действия.
- Микросервисная архитектура:
- когда много сервисов, брокеров, API, и риск расхождения контрактов высок.
Ограничения и здравый подход:
- E2E дорогие:
- медленные;
- требуют сложного стенда;
- нестабильны, если зависим от внешних систем.
- Не должны заменять unit/integration/contract-тесты:
- их задача — проверить ключевые «золотые пути» и критичные негативные сценарии.
- Хорошая стратегия:
- немного, но очень продуманных E2E-тестов на ключевые сценарии;
- логика и ветвления покрываются на уровне unit и интеграционных тестов;
- контракты между сервисами проверяются контрактными тестами.
Итого: сквозное тестирование — это проверка реальной работы системы глазами бизнеса и пользователя через все уровни и интеграции, подтверждающая, что связка архитектуры, кода и инфраструктуры корректно реализует заявленные сценарии.
Вопрос 8. Чем отличается end-to-end тест от обычного тест-кейса?
Таймкод: 00:12:18
Ответ собеседника: правильный. Уточнила, что обычный тест-кейс покрывает одно или небольшое количество требований, а end-to-end тест охватывает несколько требований и модулей, проверяя полный сквозной сценарий.
Правильный ответ:
Ключевое различие не в формальном формате "тест-кейс vs что-то другое", а в уровне охвата и цели проверки.
Важно прояснить:
- "Тест-кейс" — это форма описания теста (шаги, данные, ожидания).
- "End-to-end тест" — это тип теста по уровню охвата системы.
End-to-end тест, по сути, тоже оформляется как тест-кейс, но:
- Уровень охвата:
-
Обычный тест-кейс:
- Часто нацелен на:
- одно требование,
- один модуль,
- один API-метод,
- одну бизнес-правку.
- Может быть:
- unit-уровня (черный/белый ящик),
- интеграционного уровня,
- частичного функционального сценария.
- Пример:
- "Поля формы регистрации валидируются корректно при неверном email".
- Часто нацелен на:
-
End-to-end тест:
- Проверяет полный сквозной пользовательский или бизнес-процесс:
- от входной точки (UI/API/внешний вызов)
- до всех ключевых внутренних и внешних взаимодействий (микросервисы, БД, очереди, внешние API)
- до финального наблюдаемого результата.
- Охватывает много требований сразу:
- бизнес-правила;
- интеграции;
- права доступа;
- корректность данных по всей цепочке.
- Проверяет полный сквозной пользовательский или бизнес-процесс:
- Цель:
-
Обычный тест-кейс:
- Подтвердить корректность конкретного функционала или правила.
- Локализовать дефекты в пределах одного компонента или функции.
-
End-to-end тест:
- Подтвердить, что вся система в реальной конфигурации:
- выполняет целевой сценарий без разрывов;
- корректно интегрирована;
- не ломает критичные бизнес-потоки.
- Это "проверка боевой готовности" ключевых сценариев.
- Подтвердить, что вся система в реальной конфигурации:
- Контекст микросервисов и Go/Backend:
Пример обычного тест-кейса (интеграционный, описывает один аспект):
- Проверить, что метод
/api/v1/users:- при передаче корректных данных создает пользователя;
- возвращает 201 и JSON с id.
Пример end-to-end теста:
Сценарий: "Регистрация пользователя и активация через email".
- Шаги:
-
- Вызвать публичный API регистрации:
POST /api/v1/users
Body: { "email": "user@example.com", "password": "StrongP@ss1" }
→ Ожидаем 201. -
- Убедиться, что:
-
запись появилась в
usersв PostgreSQL со статусом "pending":SELECT status FROM users WHERE email = 'user@example.com';
-- Ожидаем: pending
-
- Проверить, что сервис нотификаций отправил письмо:
- через mock SMTP или тестовый инбокс.
-
- Перейти по ссылке активации (эмуляция клика по токену).
-
- Проверить, что статус пользователя стал "active" в БД.
-
- Проверить, что пользователь может авторизоваться и получить токен.
-
Этот сценарий:
- проходит через несколько сервисов (auth, users, mail);
- использует БД, очереди/события (если есть), внешнюю инфраструктуру;
- объединяет несколько бизнес-требований: регистрация, активация, безопасность, корректность статусов.
- Практический вывод:
- End-to-end тест:
- всегда "над" множеством обычных тест-кейсов;
- не заменяет их, а проверяет, что при собранной системе все части правильно взаимодействуют.
- Обычные тест-кейсы:
- строят фундамент стабильности на уровне модулей и функций.
- Зрелый подход:
- много быстрых и точечных unit/integration тест-кейсов;
- ограниченный, тщательно отобранный набор E2E тестов на критичные бизнес-сценарии.
Вопрос 9. Что такое интеграционное тестирование и какие основные подходы его проведения существуют?
Таймкод: 00:13:00
Ответ собеседника: правильный. Определяет интеграционное тестирование как проверку взаимодействия компонентов (UI с бэком, бэк с БД, внутренние и внешние системы). Перечисляет методы: "большой взрыв", снизу вверх (с драйверами), сверху вниз (с заглушками) и комбинированный (сэндвич/гибрид).
Правильный ответ:
Интеграционное тестирование — это проверка корректности взаимодействия между несколькими компонентами системы: сервисами, модулями, БД, очередями, внешними API, адаптерами. Цель — поймать дефекты "на стыках": несоответствие контрактов, схем, протоколов, ошибок обработки, транзакций, таймаутов и т.д.
Если unit-тесты отвечают на вопрос "корректно ли работает логика модуля в изоляции?", то интеграционные тесты проверяют: "корректно ли эти модули/сервисы работают вместе в реальных условиях взаимодействия?"
Ключевые цели интеграционного тестирования:
- Проверка контрактов:
- сигнатуры и схемы API (REST, gRPC);
- форматы сообщений (Kafka, очереди, webhooks);
- схемы БД, миграции, индексы.
- Проверка инфраструктурных аспектов:
- корректность настроек соединений (URL, порты, TLS, креды);
- работа транзакций;
- поведение при ошибках (сетевые сбои, timeouts, retries).
- Выявление расхождений между ожиданиями команд:
- backend vs frontend;
- сервис vs сервис;
- сервис vs внешняя система.
Классические методы интеграционного тестирования:
- Подход "Большой взрыв" (Big Bang):
Суть:
- Все (или почти все) модули/сервисы интегрируются сразу и тестируются как единое целое.
Плюсы:
- Быстрый старт, если уже есть собранное окружение.
Минусы:
- Очень сложно локализовать дефекты: непонятно, на стыке каких компонент сломалось.
- Высокая стоимость поддержки, нестабильность.
- В микросервисной архитектуре превращается в хрупкие e2e, если нет системного подхода.
Использовать умеренно:
- Для финальной проверки, но не как основной метод.
- Снизу вверх (Bottom-Up):
Суть:
- Сначала интегрируются и тестируются низкоуровневые компоненты:
- репозитории с реальной БД;
- адаптеры к внешним API;
- клиенты Kafka/очередей.
- Более высокоуровневые компоненты подключаются позже.
- Для имитации вызовов сверху используются драйверы (test harness).
Плюсы:
- Ранняя проверка фундаментальных компонентов.
- Хорошо подходит для backend-инфраструктуры:
- проверка миграций, схем, индексов;
- проверка клиентов БД/очередей.
Минусы:
- Сценарии верхнего уровня видны поздно.
- Иногда сложно моделировать "настоящие" сценарии без готового верхнего слоя.
Go-пример (bottom-up, тест репозитория с реальной PostgreSQL, но без поднятого всего сервиса):
func TestUserRepository_CreateAndGet(t *testing.T) {
db := openTestPostgres(t) // поднимаем контейнер, применяем миграции
repo := NewUserRepository(db)
ctx := context.Background()
user := User{Email: "test@example.com"}
require.NoError(t, repo.Create(ctx, user))
got, err := repo.GetByEmail(ctx, "test@example.com")
require.NoError(t, err)
require.Equal(t, user.Email, got.Email)
}
Этот тест уже интеграционный: он проверяет связку код + реальная БД + миграции.
- Сверху вниз (Top-Down):
Суть:
- Начинают с верхнего уровня:
- API, сервисы, сценарии.
- Нижележащие компоненты, которые еще не готовы (БД, внешние сервисы), подменяются заглушками (stubs/mocks/fakes).
- Затем заглушки постепенно заменяются реальными реализациями.
Плюсы:
- Ранний фокус на бизнес-сценариях и API.
- Можно валидировать дизайн контрактов до реализации всех зависимостей.
Минусы:
- Риск, что заглушки будут "слишком оптимистичными" и не отразят реальное поведение.
- Нужна дисциплина, чтобы потом заменить их реальными компонентами и не жить в "фейковом мире".
- Комбинированный (Сэндвич / Hybrid):
Суть:
- Сочетание подходов сверху-вниз и снизу-вверх.
- Пример:
- уже протестировали слой работы с БД (bottom-up);
- параллельно тестируем верхний уровень API с заглушками для внешних сервисов (top-down);
- затем стыкуем всё вместе.
Плюсы:
- Баланс: фундамент проверен, сценарии видны.
- Параллельная разработка и тестирование.
Минусы:
- Сложнее в организации, требует продуманной стратегии.
Реальные практики интеграционного тестирования в современных проектах:
Помимо классических подходов, в инженерной практике широко используются:
-
Контрактное тестирование (Consumer-Driven Contracts):
- Особенно важно в микросервисах.
- Идея: сервис-потребитель формализует свои ожидания к API/событиям сервиса-поставщика.
- Эти контракты автоматически проверяются против реализации.
- Снижает количество дефектов на интеграции и делает E2E легче.
-
Тесты с реальными зависимостями в изолированном окружении:
- Использование Docker / Testcontainers:
- реальная PostgreSQL;
- локальная Kafka/Redis;
- запуск Go-сервисов рядом.
- Это даёт "настоящую" интеграцию без зависимости от общего стенда.
- Использование Docker / Testcontainers:
Go-пример интеграции HTTP-сервиса с реальной БД (упрощенно):
func TestCreateUser_Integration(t *testing.T) {
db := openTestPostgres(t)
userRepo := NewUserRepository(db)
userService := NewUserService(userRepo)
handler := NewHTTPHandler(userService)
srv := httptest.NewServer(handler.Routes())
defer srv.Close()
body := `{"email":"int-test@example.com"}`
resp, err := http.Post(srv.URL+"/users", "application/json", strings.NewReader(body))
require.NoError(t, err)
require.Equal(t, http.StatusCreated, resp.StatusCode)
// Проверяем, что пользователь реально записан в БД
ctx := context.Background()
u, err := userRepo.GetByEmail(ctx, "int-test@example.com")
require.NoError(t, err)
require.Equal(t, "int-test@example.com", u.Email)
}
Здесь мы:
- поднимаем реальную БД;
- запускаем HTTP-слой;
- проверяем интеграцию HTTP → сервис → репозиторий → БД.
Ключевые инженерные моменты:
- Интеграционные тесты:
- должны быть детерминированными (контролируемые данные, изолированная среда);
- должны проверять реальные контракты, схемы и конфигурации;
- не заменяют unit-тесты, а дополняют их.
- Не стоит превращать все интеграционные тесты в "мини-E2E":
- целитесь в конкретные интеграции: сервис-БД, сервис-Kafka, сервис-сервис.
- В зрелой стратегии качества:
- Unit-тесты — много и быстрых.
- Интеграционные — целевые, на критичные стыки.
- Контрактные — на границах микросервисов.
- E2E — немного, только на ключевые бизнес-потоки.
Такое понимание и осознанный выбор метода позволяет построить надежный, прогнозируемый и поддерживаемый набор тестов, который реально защищает систему от интеграционных дефектов.
Вопрос 10. Используешь ли ты исследовательское тестирование и какие техники в него входят?
Таймкод: 00:15:48
Ответ собеседника: неполный. Описывает практическое применение: в начале проекта проходит сценарии, по ходу формирует чек-листы и тест-кейсы, использует техники тест-дизайна и фиксирует результаты. При этом отмечает, что формальные техники исследовательского тестирования по названиям не знает.
Правильный ответ:
Исследовательское тестирование — это целенаправленное, но гибкое тестирование, при котором планирование, выполнение и анализ тестов происходят одновременно. Его цель — быстро изучить продукт, найти нетривиальные дефекты, проверить гипотезы и углы, которые часто не покрываются формальными тест-кейсами.
Ключевые принципы:
- Активное мышление и любопытство: тестировщик не "прогоняет список шагов", а исследует систему, задает вопросы и проверяет гипотезы.
- Обратная связь в реальном времени:
- во время исследования уточняются требования;
- выявляются пробелы в UX, безопасности, данных;
- формируются идеи для автотестов и регрессионных сценариев.
- Лёгкое документирование:
- вместо тяжёлых формальных кейсов — заметки, чек-листы, mind map, отчеты с найденными проблемами и покрытиями.
Исследовательское тестирование особенно полезно:
- в начале работы с новым продуктом или доменом;
- при проверке сложных сценариев и интеграций (микросервисы, очереди, внешние API);
- после крупных изменений и рефакторингов;
- когда требования сырые, неполные или часто меняются.
Основные техники и форматы исследовательского тестирования:
- Сессионное исследовательское тестирование (Session-based testing)
- Работа делится на четко ограниченные по времени сессии (обычно 60–120 минут).
- У каждой сессии есть:
- charter (цель): что мы исследуем;
- scope: область (модуль, сервис, сценарий);
- ограничения: что не трогаем;
- отчет: что проверили, что нашли, какие риски увидели.
- Пример charter:
- "Исследовать создание и редактирование заказов через API: граничные значения, некорректные данные, поведение при недоступности внешнего сервиса оплат".
Это добавляет управляемость и прозрачность, в отличие от "хаотичного потыкать интерфейс".
- Туристическое тестирование (Tours)
Используются "туры" — метафорические маршруты, чтобы системно покрывать продукт под разными углами:
- Data Tour:
- исследование поведения с разными наборами данных:
- минимальные/максимальные значения;
- пустые коллекции;
- массовые данные;
- "грязные" данные.
- исследование поведения с разными наборами данных:
- Interrupted Tour:
- проверки при обрыве соединения, таймаутах, ошибках внешних сервисов.
- Configuration Tour:
- разные роли, настройки, фича-флаги.
- Money Tour:
- всё, что связано с деньгами, биллингом, ключевыми метриками бизнеса.
Для backend-систем и Go-сервисов туры хорошо ложатся на:
- сценарии с отказами (circuit breaker, ретраи);
- неконсистентные данные в БД;
- задержки/ошибки Kafka, Redis, внешних API.
- Чартеры (test charters)
Чартер — это мини-спецификация для одной исследовательской сессии:
- Что исследуем.
- Какой риск/гипотезу хотим проверить.
- Какие ограничения.
- Что считаем успехом.
Примеры чартеров:
- "Проверить API авторизации на обработку некорректных токенов, истекших токенов и токенов несуществующих пользователей."
- "Исследовать поведение сервиса заказов при массовом создании заказов и временной недоступности сервиса оплаты."
- Использование техник тест-дизайна внутри исследовательского тестирования
Исследовательское тестирование — не хаос. В нем осознанно применяются классические техники:
- Эквивалентное разбиение и граничные значения:
- для входных данных, параметров API, фильтров, пагинации.
- Попарное / комбинаторное тестирование:
- чтобы эффективно покрывать комбинации параметров.
- Ошибочные предположения (Error Guessing):
- осознанная проверка типичных "болей":
- null/empty;
- дубли;
- конкурирующие запросы;
- нарушение предположений о порядке.
- осознанная проверка типичных "болей":
- Инструменты и практики в контексте web/backend
Исследовательский подход усиливается при грамотном использовании инструментов:
- Для фронта:
- DevTools (Network, Storage, Console);
- отслеживание запросов/ответов, статусов, заголовков, кэша.
- Для backend и API:
- Postman/Insomnia, curl, HTTP-клиенты;
- манипуляции телом запросов, заголовками, последовательностью вызовов.
- Для БД:
-
прямой анализ данных в PostgreSQL:
SELECT status, COUNT(*)
FROM orders
GROUP BY status; -
поиск аномалий: дубли, некорректные статусы, невалидные связи.
-
- Для асинхронных систем:
- проверка задержек обработки;
- повторная отправка сообщения;
- симуляция ошибочных payload-ов.
Как это увязать с процессом:
- В начале проекта:
- использовать исследовательское тестирование для:
- понимания продукта;
- выявления скрытых требований и рисков;
- генерации формальных тест-кейсов и сценариев для регрессии.
- использовать исследовательское тестирование для:
- В зрелом процессе:
- сочетать:
- формальные сценарии (чек-листы, автотесты),
- регулярные исследовательские сессии по зонам риска, новым фичам и инцидентам.
- сочетать:
Сильный ответ на интервью показывает:
- Понимание, что исследовательское тестирование — осознанная дисциплина, а не "просто покликать".
- Умение:
- формулировать charters;
- применять техники тест-дизайна в исследовательском формате;
- документировать результаты так, чтобы на их основе строились автотесты, регрессия и изменение требований.
- Использование этого подхода для сложных систем: микросервисы, очереди, интеграции, большие данные, критичные бизнес-процессы.
Вопрос 11. В чем разница между смоук-тестированием и регрессионным тестированием?
Таймкод: 00:17:29
Ответ собеседника: неполный. Смоук-тест описан как проверка ключевой функциональности (упомянуты архитектура, безопасность, производительность и основные функции), регрессионное — как проверка, что существующий функционал не сломался после изменений. В описании смоук-тестирования намешаны лишние аспекты и пересечения с другими видами тестов.
Правильный ответ:
Смоук-тестирование и регрессионное тестирование решают разные задачи по глубине и охвату.
Смоук-тестирование (Smoke Testing):
Суть:
- Быстрая поверхностная проверка того, что сборка/релиз в принципе работоспособны и система "заводится".
- Ответ на вопрос:
- "Имеет ли смысл идти дальше в более глубокое тестирование/деплой или билд сразу бракованный?"
Основные характеристики:
- Минимальный набор критичных проверок:
- сервисы поднимаются;
- основные эндпоинты отвечают (health-check, базовые операции);
- можно войти в систему (auth);
- базовый ключевой сценарий работает (например, создать сущность).
- Проводится:
- после сборки и деплоя на тестовое окружение;
- перед началом полноценного функционального/регрессионного тестирования;
- часто автоматизирован (CI/CD pipeline).
- Не про глубину и не про полный набор нефункциональных проверок:
- не обязан включать детальное performance, security, сложные edge cases;
- важно не превратить смоук в полный регресс — он должен быть быстрым.
Примеры смоук-тестов для backend/микросервисов:
-
Проверка health-check эндпоинтов:
GET /healthz → 200 OK -
Базовая CRUD-операция:
POST /api/v1/users → 201
GET /api/v1/users/{id} → 200 -
Проверка подключения к PostgreSQL и очередям (Kafka/Redis) через встроенные self-check-и.
Частый практический паттерн:
- В CI:
- подняли сервисы в Docker/Kubernetes;
- прогнали быстрые smoke-скрипты;
- только если все ок — запускаем полный набор тестов.
Регрессионное тестирование (Regression Testing):
Суть:
- Проверка того, что новые изменения (фичи, багфиксы, рефакторинг, миграции) не сломали уже существующий функционал.
- Ответ на вопрос:
- "Система все еще делает корректно то, что умела делать до изменений?"
Основные характеристики:
- Охватывает более широкий набор сценариев:
- ключевые пользовательские потоки;
- интеграции между сервисами;
- критичные бизнес-правила;
- часто — комбинации разных функциональностей.
- Может включать:
- ручные тесты;
- автотесты (unit, integration, API, UI);
- контрактные тесты между микросервисами.
- Запускается:
- перед релизом;
- после значимых изменений;
- регулярно (например, nightly builds).
Примеры регрессионных проверок:
- После изменения логики скидок:
- проверить, что не сломались ранее корректные расчеты для других типов заказов.
- После миграции БД:
- убедиться, что:
- старые запросы работают;
- данные читаются без искажений;
- старые API-сценарии возвращают корректные результаты.
- убедиться, что:
Связка с Go/микросервисами:
-
Регрессия в коде:
-
прогон unit-тестов:
go test ./... -
интеграционные тесты с реальной PostgreSQL/Kafka;
-
E2E/API автотесты на ключевые сценарии.
-
-
Регрессия на уровне контрактов:
- если сервис меняет API или формат сообщения,
- контрактные тесты гарантируют, что потребители не сломаются.
Кратко и по сути:
-
Смоук-тесты:
- быстрые, узкие, минимально необходимый набор;
- цель — отсеять "мертвые" билды;
- запускаются чаще (каждый билд/деплой).
-
Регрессионные тесты:
- широкие и глубже по покрытию;
- цель — гарантировать стабильность существующего функционала после изменений;
- запускаются перед релизами, по расписанию или при существенных изменениях.
Важно не путать:
- Смоук — "жив ли пациент?"
- Регрессия — "после лечения у пациента не отказали другие органы, которые раньше работали нормально?".
Вопрос 12. Чем отличаются смоук-, регрессионное и санити-тестирование и как их правильно применять на практике?
Таймкод: 00:17:29
Ответ собеседника: неполный. Корректно различает смоук (оценка пригодности билда) и регрессионное тестирование (проверка, что изменения не сломали существующий функционал), описывает практический флоу (смоук → проверка фичи → регресс на предпроде → смоук на проде). Санити описывает как более детальную проверку отдельной функциональности на стабильном билде, но местами смешивает с обычным функциональным тестированием.
Правильный ответ:
Разница между этими типами тестирования — в цели, глубине, охвате и моменте применения в процессе.
Смоук-тестирование (Smoke Testing):
Суть:
- Быстрая, поверхностная проверка того, что сборка:
- поднимается,
- доступна,
- выполняет базовые критичные операции.
- Цель:
- решить, стоит ли вкладываться во время более глубокого тестирования или билд сразу бракованный.
Характеристики:
- Небольшой набор чеков.
- Фокус на "жизнеспособности":
- сервисы стартуют;
- основные эндпоинты отвечают;
- можно залогиниться;
- базовый сценарий "создать и прочитать объект" работает.
- Запускается:
- на каждом новом билде/деплое (test/stage);
- перед запуском регрессионных тестов;
- после выката на прод — как post-deploy smoke.
Пример для backend:
GET /health→ 200 OKPOST /api/v1/users→ 201GET /api/v1/users/{id}→ 200
Смоук не уходит в сложные комбинации, негативные сценарии, rare cases.
Регрессионное тестирование (Regression Testing):
Суть:
- Проверка того, что новые изменения (фичи, багфиксы, миграции, рефакторинг) не сломали уже существующий функционал.
- Цель:
- сохранить стабильность системы при непрерывных изменениях.
Характеристики:
- Широкий охват:
- ключевые пользовательские и бизнес-сценарии;
- интеграции между микросервисами;
- критичные правила, расчеты, права доступа.
- Включает:
- автотесты (unit, integration, API, UI, контрактные);
- при необходимости ручные проверки по чек-листам.
- Запускается:
- перед релизами;
- после крупных изменений;
- часто — в nightly/CI пайплайнах.
Практически:
- Для Go/микросервисов:
go test ./...+ интеграционные тесты (PostgreSQL, Kafka, Redis);- E2E / API автотесты по критичным сценариям.
Санити-тестирование (Sanity Testing):
Суть:
- Узконаправленная, быстрая проверка конкретной части функциональности после изменений.
- По сути, это адресная проверка "здравости" изменений:
- убедиться, что:
- исправленный баг действительно исправлен,
- новая/измененная фича в рамках своих основных сценариев работает корректно,
- при этом сборка в целом достаточно стабильна, чтобы не запускать полный регресс зря.
- убедиться, что:
Важно:
- Санити — это не полный регресс и не смоук:
- смоук отвечает: "жив ли билд в принципе?";
- санити отвечает: "выглядят ли конкретные изменения рабочими и непротиворечивыми?";
- Обычно выполняется:
- после новой сборки с конкретными изменениями;
- перед решением: "запускать ли полный регресс?" или "готово к выкатке?".
Примеры:
-
Фикс бага:
- Баг: при смене пароля не инвалидируются старые токены.
- Санити:
- сменить пароль → проверить, что старый токен больше не работает;
- проверить 1–2 основных связанных сценария (логин/логаут), но не весь функционал auth.
-
Новая фича:
- Добавили поле "phone" у пользователя.
- Санити:
- создать пользователя с phone → проверить в API и БД;
- обновить phone → проверить отображение и валидацию;
- не идем по всему регрессу профиля, платежей, заказов и т.д.
Как это применять на практике (зрелый процесс):
Разумная последовательность для фичи/релиза может выглядеть так:
-
На test/stage окружении:
- Смоук-тест:
- убедиться, что сервисы поднялись, базовые функции работают.
- Санити-тест:
- проверить новую фичу или фиксы по основным сценариям;
- убедиться, что изменения не ломают себя же.
- Если санити провален — нет смысла запускать полный регресс.
- Смоук-тест:
-
Если санити успешен:
- Регрессионное тестирование:
- прогон автотестов;
- при необходимости целевые ручные проверки по зонам риска.
- Регрессионное тестирование:
-
Перед и после выката на prod:
- Pre-prod:
- смоук + ключевые регрессионные сценарии.
- Prod:
- post-deploy смоук:
- health-check,
- критичные бизнес-операции на тестовых или безопасных сценариях.
- post-deploy смоук:
- Pre-prod:
Кратко:
-
Смоук:
- "Жив ли билд? Имеет ли смысл тестировать дальше?"
- Быстро, поверхностно, по ключевым точкам.
-
Санити:
- "То, что только что поменяли, вообще работает по-основному?"
- Узко, целево, вокруг изменений/фиксов.
-
Регрессия:
- "Не сломали ли мы что-нибудь из того, что раньше работало?"
- Широко, глубоко, на весь важный функционал.
Такое различение и правильное применение трех уровней позволяет строить эффективный пайплайн: быстро отсекать бракованные сборки, точечно проверять изменения и гарантировать стабильность продукта перед релизом.
Вопрос 13. Какие виды тестирования относятся к нефункциональным?
Таймкод: 00:19:35
Ответ собеседника: правильный. Перечисляет тестирование восстановления и отказоустойчивости, производительности (нагрузочное, стресс, объёмное, конкурентное, масштабируемость, стабильность), безопасности, удобства использования, интерфейса, установки, локализации, интернационализации, доступности и кроссбраузерности.
Правильный ответ:
Нефункциональное тестирование оценивает не то, "что" система делает (функциональность), а "как" она это делает в реальных условиях эксплуатации. Это про качество сервиса: скорость, надежность, устойчивость, безопасность, удобство, совместимость, эксплуатационные характеристики. В контексте современных web-систем, микросервисов и backend-сервисов на Go это критично.
Ниже — ключевые виды нефункционального тестирования с практическими акцентами.
Производительность (Performance Testing)
Включает несколько подтипов:
-
Нагрузочное тестирование (Load Testing):
- Проверяет, как система работает под ожидаемой (нормальной) и слегка повышенной нагрузкой.
- Цель: убедиться, что при целевых RPS/пользователях сохраняются:
- адекватное время ответа;
- отсутствие деградации и ошибок.
- Пример:
- 500 RPS к
/api/v1/orders, p95 < 300 мс, error rate < 1%.
- 500 RPS к
-
Стресс-тестирование (Stress Testing):
- Проверка за пределами плановой нагрузки, до отказа.
- Цель:
- понять границы;
- проверить поведение при перегрузке;
- убедиться в корректной деградации (rate limiting, graceful fail).
-
Объёмное тестирование (Volume Testing):
- Проверка работы с большими объемами данных:
- размер БД;
- большие очереди;
- большие файлы.
- Цель:
- оценить влияние объема данных на производительность и стабильность.
- Проверка работы с большими объемами данных:
-
Тестирование стабильности/долговременное (Soak / Endurance Testing):
- Длительный прогон под стабильной или переменной нагрузкой.
- Цель:
- найти утечки памяти, накопительные эффекты (goroutine leak, connection leak), деградацию.
- Для Go-сервисов особенно полезно:
- отлавливать утечки горутин и некорректные таймауты.
-
Тестирование масштабируемости (Scalability Testing):
- Проверяет, как система масштабируется по горизонтали/вертикали.
- Цель:
- понять, линейно ли растут ресурсы к нагрузке;
- найти bottlenecks (БД, локи, глобальные ресурсы).
Безопасность (Security Testing)
Проверяет устойчивость к уязвимостям и соблюдение политик безопасности.
Ключевые направления:
- Аутентификация и авторизация:
- корректность JWT/OAuth, ролей, прав.
- Управление сессиями:
- фиксация сессий, истечение, refresh-токены.
- Валидация входных данных:
- защита от XSS, SQL Injection, Command Injection, XXE.
- Защита API:
- rate limiting, CORS, безопасные заголовки.
- Шифрование:
- HTTPS, защита чувствительных данных в хранилищах.
- Анализ зависимостей:
- SCA (проверка уязвимостей в библиотеках).
Для Go:
- использование
contextи таймаутов; - аккуратная работа с SQL (подготовленные выражения, параметризация);
- проверка конфигураций (TLS, секреты не в коде).
Отказоустойчивость и восстановление (Reliability, Resilience, Recovery)
- Failover / High Availability:
- поведение при падении узлов, рестартах контейнеров, перезапусках подов.
- Recovery Testing:
- восстановление после сбоев:
- перезапуск сервисов;
- восстановление БД из бэкапов;
- реплей сообщений из очередей.
- восстановление после сбоев:
- Chaos/Resilience Testing:
- искусственное внесение сбоев:
- задержки, падения зависимостей, сетевые проблемы;
- проверка, что система:
- деградирует контролируемо,
- не теряет данные,
- корректно восстанавливается.
- искусственное внесение сбоев:
Пример практического SQL аспекта:
- Проверка, что после падения и восстановления БД:
- транзакции корректно откатились/зафиксировались:
SELECT COUNT(*)
FROM orders
WHERE status = 'processing'
AND updated_at < NOW() - INTERVAL '10 minutes';
-- Подозрительные "зависшие" заказы могут указывать на проблемы восстановления.
Юзабилити и UX (Usability Testing)
- Оценивает удобство использования:
- понятность интерфейса;
- логичность сценариев;
- количество шагов для ключевых операций;
- ошибки, которые провоцирует UI.
- Особенно важно для клиентских UI, но и для админок, API-консолей, внутренних инструментов.
Интерфейс и совместимость (Compatibility Testing)
- Кроссбраузерное тестирование:
- Chrome, Firefox, Safari, Edge, мобильные браузеры.
- Кроссплатформенность:
- разные ОС, устройства, разрешения.
- Совместимость API:
- backward compatibility для клиентов: новые версии API не ломают старые интеграции.
Локализация и интернационализация (L10n, i18n)
- Локализация:
- корректность переводов, форматов дат, чисел, валют.
- Интернационализация:
- готовность системы к разным языкам, кодировкам, правописаниям, таймзонам.
Доступность (Accessibility Testing)
- Проверка доступности для людей с ограниченными возможностями:
- поддержка screen reader;
- контраст;
- навигация с клавиатуры;
- корректная семантика HTML.
- Соответствие стандартам (например, WCAG).
Установка, конфигурация и эксплуатация (Installation, Configuration, Maintainability)
- Тестирование установки и обновления:
- миграции схем БД;
- катящиеся релизы, откаты.
- Тестирование конфигурации:
- корректная работа с разными env, флагами, фича-флагами.
- Observability:
- наличие и корректность логов, метрик, трассировок;
- пригодность для эксплуатации и диагностики.
Пример метрик для Go/микросервисов:
- время ответа API;
- количество 5xx;
- количество goroutines;
- использование памяти и CPU;
- lag консюмеров Kafka.
Кратко:
К нефункциональному тестированию относятся, в частности:
- производительность (нагрузочное, стресс, объемное, стабильность, масштабируемость);
- безопасность;
- отказоустойчивость, надежность, восстановление;
- юзабилити и UX;
- доступность;
- кроссбраузерность и совместимость;
- локализация и интернационализация;
- установка, обновление, конфигурация;
- эксплуатационная пригодность (логирование, мониторинг).
Зрелый подход к качеству обязательно включает планирование и автоматизацию нефункциональных проверок, особенно для ключевых сервисов и сценариев, а не ограничивается только функциональными тестами.
Вопрос 14. Какие техники тест-дизайна ты знаешь и какие используешь чаще всего?
Таймкод: 00:20:12
Ответ собеседника: правильный. Перечисляет: граничные значения, классы эквивалентности, попарное тестирование, таблицы принятия решений, диаграммы состояний и переходов, причинно-следственные связи, предугадывание ошибок. Говорит, что чаще использует граничные значения и классы эквивалентности, ранее активно применяла попарное тестирование, изредка таблицы принятия решений, а причинно-следственные связи и предугадывание ошибок применяет интуитивно.
Правильный ответ:
Техники тест-дизайна помогают системно сокращать количество тестов при сохранении высокого покрытия по рискам и логике. Важно не просто перечислить названия, а понимать, где и как их эффективно применять, особенно в API, микросервисах, интеграциях, работе с БД и сложными бизнес-правилами.
Ниже — основные техники и практические акценты.
Классы эквивалентности (Equivalence Partitioning)
Суть:
- Разделяем пространство входных данных на группы (классы), внутри которых система должна вести себя одинаково.
- Достаточно взять по одному представителю из каждого класса вместо проверки всех значений.
Пример (API с возрастом 18–60):
- Классы:
- < 18 (невалидный)
- 18–60 (валидный)
- > 60 (невалидный, если по требованиям).
- Тесты:
- 17, 18, 30, 60, 61 — вместо сотен значений.
Применение:
- Поля форм, параметры API, фильтры, лимиты.
- Для контрактов между сервисами — сокращение вариантов входных значений.
Граничные значения (Boundary Value Analysis)
Суть:
- Ошибки чаще всего на границах диапазонов.
- Тестируем значения:
- чуть меньше границы;
- на границе;
- чуть больше.
Продолжая пример с возрастом (18–60):
- Тесты:
- 17, 18, 19
- 59, 60, 61
Пример для pagination API:
- page >= 1, page_size 1..100
- Тесты:
- page: 0, 1
- page_size: 0, 1, 100, 101
Для backend и Go-сервисов:
- Применимо к лимитам, timeout, size, количеству элементов, длинам строк.
Попарное тестирование (Pairwise / All-pairs)
Суть:
- При множестве параметров полный перебор комбинаций слишком дорог.
- Попарное тестирование гарантирует покрытие всех пар значений параметров при существенно меньшем количестве тестов.
Когда использовать:
- Много опций конфигурации;
- Комбинации флагов, типов клиентов, ролей, методов оплаты, валют, каналов доставки.
Пример:
- Параметры:
- Роль: [admin, user, guest]
- Тип устройства: [web, mobile]
- Локаль: [en, ru]
- Полный перебор: 3 * 2 * 2 = 12.
- Pairwise даёт меньше тестов, сохраняя покрытие всех пар (роль-устройство, роль-локаль, устройство-локаль).
Таблицы принятия решений (Decision Tables)
Суть:
- Используются, когда на результат влияет комбинация условий.
- Позволяют явно зафиксировать:
- условия (True/False, значения),
- действия/результаты.
Применение:
- Сложные бизнес-правила:
- скидки,
- тарификация,
- валидация,
- маршрутизация запросов.
Пример (упрощенный):
Условия:
- isVip
- sum > 1000
Действия:
- скидка 10%
- скидка 5%
- без скидки
Таблица помогает:
- увидеть пропущенные комбинации;
- проектировать тесты без логических дыр.
Для backend:
- Отлично подходит для тестирования сервисных методов и SQL/логики расчётов.
Диаграммы состояний и переходов (State Transition Testing)
Суть:
- Тестирование систем, где поведение зависит от состояния.
- Строим модель:
- состояния,
- события,
- переходы,
- запрещённые/некорректные переходы.
Примеры:
- Жизненный цикл заказа: created → paid → shipped → delivered → canceled.
- Жизненный цикл пользователя: pending → active → blocked → deleted.
- Авторизация: logged out / logged in / session expired.
Тесты:
- Проверяют допустимые переходы.
- Проверяют, что запрещенные переходы отклоняются корректно.
Пример SQL-проверки:
-- Проверка, что нет невозможных переходов заказа:
SELECT id, status
FROM orders
WHERE status NOT IN ('created','paid','shipped','delivered','canceled');
И unit/integration тесты в Go для сервисов, запрещающих некорректные переходы.
Причинно-следственные связи (Cause-Effect Graphing)
Суть:
- Формализует зависимость "условия → эффекты".
- Помогает при сложных правилах, где много условий влияет на исход.
Применить, когда:
- Много if/else;
- Несколько условий влияют на несколько действий (например, юридические ограничения, региональные настройки, тип клиента, статус подписки).
Результат:
- Набор тестов, покрывающих все значимые комбинации причин и следствий, в т.ч. "скрытые" ветки логики.
Предугадывание ошибок (Error Guessing)
Суть:
- Использование опыта и интуиции:
- где обычно ломается?
- какие типовые ошибки допускают разработчики?
- Примеры:
- null/empty;
- race conditions;
- неконсистентность между БД и кешем;
- неверные границы;
- неверные default-значения;
- неправильная обработка таймаутов.
Сильный подход:
- Соединять это с анализом кода, архитектуры, миграций, логов инцидентов.
- Для Go/микросервисов:
- подозрительные места: гонки, неиспользование контекста, "забытые" ошибки, неидемпотентные обработчики.
Комбинирование техник на практике
В реальных проектах разумно:
- Для входных данных API:
- классы эквивалентности + граничные значения.
- Для конфигураций и ролей:
- попарное тестирование.
- Для сложных бизнес-правил:
- таблицы принятия решений + причинно-следственные связи.
- Для жизненных циклов сущностей:
- диаграммы состояний и переходов.
- Для зон риска:
- предугадывание ошибок + анализ инцидентов и кода.
Пример применения к Go-сервису (регистрация пользователя):
Требования:
- email обязателен, валидный;
- пароль 8–64 символа;
- при существующем email — 409.
Техники:
- Классы эквивалентности:
- валидные email;
- невалидные email;
- пустой email.
- Граничные значения:
- пароль длиной 7, 8, 64, 65.
- Предугадывание ошибок:
- очень длинный email;
- спецсимволы;
- дубликаты при конкурентных запросах.
Эти техники далее ложатся в:
- unit-тесты;
- интеграционные/API тесты;
- E2E сценарии.
Хороший ответ демонстрирует не только знание названий, но и умение:
- выбирать технику под задачу;
- комбинировать их;
- применять к реальным сценариям: API, очереди, микросервисы, сложные правила, данные в БД.
Вопрос 15. Какие обязательные атрибуты должен содержать баг-репорт?
Таймкод: 00:21:27
Ответ собеседника: правильный. Перечисляет: уникальный идентификатор, краткое и полное описание, шаги воспроизведения, ожидаемый и фактический результат, ссылку на требования, при необходимости — информацию об обходе, воспроизводимость, важность, срочность, комментарии, вложения и окружение. Уточняет, что часть полей используется выборочно.
Правильный ответ:
Хороший баг-репорт — это не бюрократический документ, а инженерный артефакт, который позволяет разработчику:
- быстро понять суть проблемы;
- однозначно воспроизвести дефект;
- оценить влияние на систему;
- эффективно исправить, не тратя время на уточнения.
Минимальный обязательный набор атрибутов для практики промышленной разработки должен обеспечивать:
- однозначность;
- воспроизводимость;
- привязку к требованиям;
- понятность при чтении человеком, не присутствовавшим при обнаружении.
Ключевые обязательные атрибуты:
- Уникальный идентификатор
- Генерируется системой трекинга (Jira, YouTrack и т.п.).
- Нужен для ссылок в:
- коммитах,
- MR/PR,
- отчётах,
- релиз-нотах.
Пример: PAY-1243, API-567.
- Краткое описание (Summary / Title)
- Сжато и конкретно отражает проблему.
- Формат:
- "Где? Что не так? При каких условиях (если влезает)?"
- Хороший пример:
- "API /orders/{id}: 500 при запросе несуществующего id"
- "UI: дубль платежа при двойном клике по кнопке 'Оплатить'"
- Плохой пример:
- "Не работает", "Ошибка", "Баг".
- Окружение (Environment)
Обязательно, особенно для распределенных систем:
- стенд: dev, test, stage, prod;
- версия приложения/сервиса (build number, git commit, tag);
- важные параметры:
- браузер и версия;
- ОС;
- моб. устройство;
- конфигурация (feature flags, регион, тип клиента);
- версия API (v1/v2).
Для backend/Go-сервисов:
- указать:
- сервис/микросервис;
- версию docker-образа;
- важные фича-флаги.
- Шаги воспроизведения (Steps to Reproduce)
- Четкая, по возможности нумерованная последовательность действий.
- Не "сделать как обычно", а конкретно:
- входные данные;
- вызовы API;
- навигация по UI;
- предварительные условия (preconditions).
Примеры:
-
Для UI:
-
- Зайти под пользователем user@test.com
-
- Перейти в "Мои заказы"
-
- Нажать "Создать заказ" с параметрами ...
-
- Нажать "Сохранить"
-
-
Для API:
-
указать точный запрос:
POST /api/v1/orders
Body: { "user_id": -1, "amount": 100 }
-
- Ожидаемый результат
- Конкретное описание, основанное:
- на требованиях,
- спецификациях,
- согласованных бизнес-правилах.
- Не "должно работать", а:
- "Должен вернуться HTTP 400 с кодом ошибки 'INVALID_USER_ID'".
Это помогает разработчику понять:
- какое поведение считается корректным;
- нет ли ошибки в ожиданиях тестировщика.
- Фактический результат
- Реально наблюдаемое поведение.
- С максимальной конкретикой:
- текст сообщений;
- коды ответов;
- изменения в данных;
- видимые эффекты в UI.
Пример:
- "Возвращается 500 Internal Server Error, в логах 'pq: null value in column "user_id"'".
- Воспроизводимость (Reproducibility)
- Насколько стабилен дефект:
- всегда,
- иногда (процент),
- при определенных условиях (нагрузка, конкретные данные).
- Для нестабильных багов:
- указать, сколько раз пробовали и сколько раз поймали.
Это влияет на:
- приоритизацию;
- подход к диагностике (логи, метрики, профилирование).
- Важность и приоритет (Severity / Priority)
Должны быть зафиксированы явно:
- Severity (насколько серьезны последствия):
- Blocker, Critical, Major, Minor, Trivial.
- Priority (как быстро исправлять):
- P0, P1, P2...
Примеры:
- Blocker/Critical:
- падение сервиса,
- потеря данных,
- невозможность оформить заказ/оплатить.
- Minor:
- некорректный отступ в UI,
- опечатка в тексте.
Важно:
- Severity оценивает тестировщик (по воздействию).
- Priority обычно уточняет владелец продукта / тимлид / менеджер.
- Ссылки на требования / спецификации / макеты
- Желательно всегда указывать, на основании чего считаем поведение некорректным:
- Jira-история;
- Confluence-страница;
- API-спецификация (OpenAPI/Swagger);
- дизайн в Figma.
Это:
- уменьшает дискуссии "это баг или фича";
- помогает синхронизировать ожидания.
- Вложения и дополнительная диагностика
Не формально "опционально", а практически критично:
-
Скриншоты, видео (UI-ошибки, сложные сценарии).
-
Логи:
- frontend (console),
- backend (фрагменты логов с trace-id),
- system logs.
-
Запросы/ответы API (curl/Postman экспорт).
-
SQL-снимки (осторожно с чувствительными данными):
SELECT id, status, amount
FROM orders
WHERE id = 123; -
Trace-id / correlation-id:
- чтобы разработчик быстро нашёл соответствующие логи и трассировки.
Пример хорошего баг-репорта (упрощенно):
- Summary:
- "POST /orders возвращает 500 при несуществующем user_id"
- Environment:
- stage, service v1.2.3, commit abc123, feature-flag new-order-flow=on
- Steps:
-
- POST /api/v1/orders с body:
{ "user_id": 999999, "amount": 100 }
-
- Expected:
- HTTP 400 с кодом
USER_NOT_FOUND.
- HTTP 400 с кодом
- Actual:
- HTTP 500, в логах:
pq: insert or update on table "orders" violates foreign key constraint "orders_user_id_fkey".
- HTTP 500, в логах:
- Severity:
- Major (ошибка сервера, неправильная обработка валидации).
- Links:
- Jira:
PROJ-101, API spec: Confluence link.
- Jira:
- Attachments:
- curl-запрос, фрагмент логов, trace-id.
Итог:
Обязательные атрибуты хорошего баг-репорта:
- идентификатор;
- ясный заголовок;
- окружение;
- четкие шаги воспроизведения;
- ожидаемый результат;
- фактический результат;
- данные о воспроизводимости;
- severity (и, желательно, priority);
- ссылка на требования/спецификацию;
- вложения (логи, скриншоты, запросы) для ускорения анализа.
Такая структура минимизирует "ping-pong" между QA и разработчиками и ускоряет цикл обнаружения–исправления–проверки дефектов.
Вопрос 16. Какой уровень серьёзности и приоритета у дефекта с пропущенной запятой в описании товара на сайте, и как это обосновать?
Таймкод: 00:22:18
Ответ собеседника: правильный. Относит орфографическую ошибку к низкой серьёзности, так как не влияет на функциональность, но допускает высокий приоритет из-за влияния на имидж и восприятие компании пользователями.
Правильный ответ:
Такой дефект обычно классифицируется как:
- серьёзность (Severity): низкая (Minor/Trivial);
- приоритет (Priority): зависит от контекста, может быть от низкого до высокого.
Обоснование по уровням:
- Серьёзность (Severity):
- Критерий: насколько дефект влияет на работоспособность и бизнес-логику.
- Пропущенная запятая:
- не ломает функционал;
- не мешает оформлению заказов;
- не искажает юридически значимые условия (в типичном случае);
- не ведёт к потере данных или денег.
- Следовательно:
- обычно это Minor или Trivial:
- дефект интерфейса/контента;
- косметический, не функциональный.
- обычно это Minor или Trivial:
Исключения:
- Если пунктуация меняет смысл так, что:
- может ввести в заблуждение о цене, условиях акции, юридических обязательствах,
- или нарушает юридически важный текст,
- тогда серьёзность может быть выше (до Major), но это особый случай и должен быть обоснован.
- Приоритет (Priority):
Приоритет определяется не техническим ущербом, а:
- влиянием на:
- репутацию бренда;
- восприятие качества;
- целевую аудиторию;
- местом и видимостью дефекта:
- главная страница,
- ключевые маркетинговые блоки,
- платные рекламные лендинги,
- банковские/юридические продукты,
- крупный B2B-клиент.
Типичные подходы:
-
Низкий/средний приоритет:
- если ошибка в малозаметном описании, на внутренней странице, не бьет по довериям.
- Может быть запланирована в один из ближайших спринтов, без экстренности.
-
Средний/высокий приоритет:
- если ошибка:
- на главной;
- в блоке, который видит каждый новый пользователь;
- в описании премиум-продукта;
- в презентации для важного партнера.
- Здесь аргументация:
- отражает уровень внимательности компании;
- может снижать доверие к бренду, особенно в чувствительных доменах (банк, медицина, госуслуги).
- если ошибка:
- Как обосновать решение на практике:
Корректный ответ на интервью должен показать умение разделять:
- Severity — про технический/функциональный ущерб.
- Priority — про бизнес, имидж, контекст и сроки.
Пример формулировки:
- Severity: Trivial (UI/content issue, функциональность не затронута).
- Priority:
- Если это главный экран/ключевой промо-блок:
- можно поставить High, с обоснованием:
- влияет на впечатление всех новых пользователей,
- выглядит непрофессионально, бьет по бренду.
- можно поставить High, с обоснованием:
- Если это глубоко внутри каталога:
- Medium или Low:
- можно исправить планово, без блокировки релиза.
- Medium или Low:
- Если это главный экран/ключевой промо-блок:
Важный момент для зрелой команды:
- Такие дефекты не должны блокировать критичные релизы, если нет искажений смысла.
- Но их стоит исправлять достаточно оперативно, особенно в публичных и заметных местах, чтобы поддерживать высокий уровень доверия и качества продукта.
Вопрос 17. Что такое динамическое тестирование и чем оно отличается от статического?
Таймкод: 00:24:16
Ответ собеседника: правильный. Описывает динамическое тестирование как выполнение проверок при запущенном приложении и активном взаимодействии с ним. Статическое — как анализ без запуска кода (требования, дизайн и т.п.).
Правильный ответ:
Динамическое и статическое тестирование отличаются тем, на каком артефакте и каким способом мы ищем дефекты.
Динамическое тестирование:
Суть:
- Проверка качества при выполнении (run-time) программы.
- Код или система запускаются, подаются входные данные, анализируется фактическое поведение.
Ключевые характеристики:
- Требует работающей сборки или хотя бы запускаемого модуля.
- Обнаруживает дефекты:
- логики;
- интеграций;
- обработки данных;
- взаимодействия с окружением (БД, сети, очереди);
- производительности и потребления ресурсов.
- Примеры:
- ручное функциональное тестирование UI и API;
- автоматизированные:
-
unit-тесты;
go test ./... -
интеграционные тесты (Go-сервис + PostgreSQL/Kafka);
-
end-to-end тесты;
-
нагрузочное и стресс-тестирование;
-
динамическое security-тестирование (сканирование живого сервиса).
-
Простой пример динамического теста на Go (unit):
func Sum(a, b int) int {
return a + b
}
func TestSum(t *testing.T) {
got := Sum(2, 3)
want := 5
if got != want {
t.Fatalf("expected %d, got %d", want, got)
}
}
- Здесь код выполняется, и мы проверяем результат во время выполнения.
Другой пример: интеграционный тест с реальной БД:
func TestCreateUser_Integration(t *testing.T) {
db := openTestPostgres(t) // старт контейнера, миграции
repo := NewUserRepository(db)
ctx := context.Background()
err := repo.Create(ctx, User{Email: "dyn@test.com"})
require.NoError(t, err)
u, err := repo.GetByEmail(ctx, "dyn@test.com")
require.NoError(t, err)
require.Equal(t, "dyn@test.com", u.Email)
}
Это динамическое тестирование: идет реальное выполнение кода, SQL, соединений.
Статическое тестирование:
Суть:
- Анализ артефактов разработки без запуска программы.
- Проверка "на бумаге" или с помощью инструментов анализа.
Что анализируется:
- Требования:
- полнота, непротиворечивость, тестопригодность.
- Архитектура и дизайн:
- корректность взаимодействий;
- расширяемость;
- отсутствие очевидных антипаттернов.
- Код:
- статический анализ;
- код-ревью;
- линтеры;
- поиск уязвимостей и дефектов стиля/логики.
Примеры инструментов для Go:
go vet— поиск подозрительных конструкций.golangci-lint— набор линтеров (стиль, ошибки, потенциальные баги).- SAST-инструменты (Static Application Security Testing) для поиска уязвимостей.
Пример статического анализа SQL:
SELECT *
FROM orders
WHERE status = 'created';
- На этапе ревью или анализатора можно:
- указать на необходимость индекса по
status, если запрос частый; - проверить, что нет
SELECT *в чувствительных контекстах.
- указать на необходимость индекса по
Ключевые отличия:
-
Выполнение:
- Статическое: без запуска кода.
- Динамическое: с запуском кода/системы.
-
Типы находимых дефектов:
- Статическое:
- дефекты требований (противоречия, дыры);
- архитектурные ошибки;
- потенциальные баги и уязвимости в коде;
- стилистические и структурные проблемы.
- Динамическое:
- реальные ошибки выполнения;
- неправильная логика;
- интеграционные проблемы;
- утечки памяти, гонки, проблемы с таймаутами;
- ошибки в реальном взаимодействии с БД и внешними сервисами.
- Статическое:
-
Момент применения:
- Статическое:
- можно и нужно применять очень рано:
- на уровне требований;
- на уровне дизайна;
- во время написания кода.
- можно и нужно применять очень рано:
- Динамическое:
- когда есть исполняемый код или собранная система.
- Статическое:
Практический вывод:
-
Статическое тестирование:
- дешевле и раньше ловит ошибки (shift left).
- Хорошое покрытие статикой и ревью уменьшает количество дефектов, доходящих до динамических тестов.
-
Динамическое тестирование:
- подтверждает, что система реально работает корректно в рантайме и боевых сценариях.
- Невозможно заменить его одной только статикой.
Зрелый процесс качества всегда сочетает оба подхода:
- статический анализ требований, архитектуры и кода;
- динамические юнит-, интеграционные, контрактные, E2E, нагрузочные и security-тесты.
Вопрос 18. Назови несколько принципов тестирования и объясни, как бороться с эффектом пестицида.
Таймкод: 00:24:48
Ответ собеседника: правильный. Перечисляет ключевые принципы: невозможность исчерпывающего тестирования, тестирование показывает наличие дефектов, а не их отсутствие, необходимость раннего тестирования, контекст-зависимость, кластеризацию дефектов, эффект пестицида и заблуждение об отсутствии ошибок. Для борьбы с эффектом пестицида предлагает менять тестовые данные и актуализировать тест-кейсы.
Правильный ответ:
Ключевые принципы тестирования (в прикладном, инженерном виде):
- Невозможность исчерпывающего тестирования
- Полностью проверить все комбинации входных данных, состояний, окружений нереально.
- Следствие:
- используем риск-ориентированный подход;
- применяем техники тест-дизайна (классы эквивалентности, граничные значения, попарное тестирование);
- фокусируемся на:
- критичных бизнес-сценариях,
- сложных интеграциях,
- исторически проблемных местах.
- Тестирование показывает наличие дефектов, а не их отсутствие
- Даже если все тесты зелёные, это не доказательство идеального качества.
- Следствие:
- важно качество самих тестов и покрытие;
- нужны разные уровни: unit, integration, contract, E2E, нефункциональные;
- анализ инцидентов и логов в проде — часть реальной картины.
- Раннее тестирование (Shift Left)
- Чем раньше найден дефект (на этапе требований, дизайна, код-ревью), тем дешевле его исправление.
- Следствие:
- ревью требований и API-спецификаций;
- статический анализ;
- unit и контрактные тесты как обязательная часть разработки;
- тестируемость и наблюдаемость закладываются в архитектуру.
- Кластеризация дефектов
- Дефекты склонны скапливаться в одних и тех же модулях:
- сложная логика,
- старый легаси-код,
- активно меняющиеся компоненты,
- низкое качество дизайна.
- Следствие:
- осознанно усиливаем тестовое покрытие этих зон;
- применяем более строгие практики: code review, дополнительные интеграционные тесты, метрики качества.
- Контекст-ориентированность
- Подходы, глубина и инструменты тестирования зависят от:
- домена (финтех, медицина, e-commerce, gov),
- типа продукта (API, B2C web, B2B-платформа, внутренний сервис),
- критичности (деньги, безопасность, SLA).
- Следствие:
- не существует универсального "чек-листа на всё";
- тестовая стратегия должна учитывать архитектуру (микросервисы, очереди, БД) и бизнес-приоритеты.
- Заблуждение об отсутствии ошибок
- "Нет найденных дефектов" != "Нет дефектов".
- Часто означает:
- слабые тесты,
- плохой охват,
- тестирование не там, где нужно.
- Следствие:
- регулярно переоцениваем покрытие;
- проверяем, соответствуют ли тесты актуальным рискам и изменившейся архитектуре;
- используем метрики (вообще находили ли мы баги в этой области, какие?).
- Эффект пестицида
Суть эффекта пестицида:
- Если долго выполнять один и тот же набор тестов без изменений:
- со временем они перестают находить новые дефекты;
- система адаптируется под эти проверки (фиксируем только то, что поймали);
- "дыры" остаются вне зоны видимости.
Это особенно заметно:
- в регрессионных наборах автотестов;
- в шаблонных smoke/regression-checklist-ах;
- при неизменяемых E2E сценариях в быстро развивающейся системе.
Как правильно с ним бороться (прикладные практики):
- Регулярный пересмотр тестов
- Периодически анализировать:
- насколько тесты соответствуют текущей архитектуре и бизнес-логике;
- покрывают ли они новые фичи, интеграции, миграции.
- Удалять устаревшие тесты, которые больше не несут ценности.
- Обновлять ожидаемые результаты и сценарии под актуальные требования.
- Расширение и варьирование тестовых данных
-
Для API, БД, микросервисов:
- менять набор входных данных:
- граничные значения;
- редкие кейсы;
- «грязные» данные;
- большие объемы/массовые операции.
- менять набор входных данных:
-
Для примера:
Вместо одного-двух "красивых" запросов:
POST /api/v1/users
{ "email": "test@example.com", "age": 25 }Добавить:
- разные домены email;
- очень длинные строки;
- unicode;
- параллельные запросы (concurrency);
- сценарии с частично валидными данными.
- Использование разных техник тест-дизайна
- Не ограничиваться только позитивными/несколькими негативными кейсами.
- Применять:
- классы эквивалентности и граничные значения;
- попарное тестирование для комбинаций;
- таблицы принятия решений и причинно-следственные связи для сложных правил;
- диаграммы состояний для жизненных циклов сущностей.
- Это позволяет выделять новые сценарии, которые старый набор тестов не покрывал.
- Инцидент-ориентированное улучшение тестов
- Каждый найденный в проде дефект:
- должен приводить не только к фиксу кода,
- но и к добавлению или корректировке тестов, которые:
- воспроизводят сценарий;
- закрывают целый класс подобных проблем.
- Это эволюционирует регрессию по реальным болям системы.
- Исследовательское тестирование (exploratory)
- Регулярно дополнять формальные автотесты исследовательскими сессиями:
- новые комбинации;
- необычные последовательности действий;
- проверки под нагрузкой, при отказах, с измененными конфигурациями.
- Особенно важно для микросервисов, асинхронных процессов, сложных интеграций.
- Включение архитектуры и логов в мышление о тестах
- Анализировать:
- где могут быть точки отказа: очереди, транзакции, ретраи, кеши, гонки.
- что показывают логи и метрики в проде:
- частые 4xx/5xx;
- timeouts;
- падения консюмеров;
- долгие запросы к БД.
- На основании этого добавлять новые тесты:
- интеграционные;
- контрактные;
- E2E сценарии на проблемных путях.
Пример эволюции тестов на Go (борьба с "пестицидом"):
-
Был только один happy-path тест:
func TestCreateOrder_OK(t *testing.T) { /* ... */ } -
После анализа инцидентов и рисков:
- добавляем:
func TestCreateOrder_InvalidUser(t *testing.T) { /* 400, USER_NOT_FOUND */ }
func TestCreateOrder_InsufficientBalance(t *testing.T) { /* 402 или бизнес-ошибка */ }
func TestCreateOrder_DuplicateRequest_Idempotency(t *testing.T) { /* не создаем дубликаты */ }
func TestCreateOrder_PaymentServiceTimeout_Retry(t *testing.T) { /* корректная обработка таймаута */ } -
Эти тесты начинают ловить классы проблем, которые старый набор не покрывал.
Итог:
- Принципы тестирования задают рамки мышления: мы понимаем ограничения, риски и роль тестирования.
- Борьба с эффектом пестицида — это:
- не разовая акция, а постоянная эволюция тестов;
- пересмотр сценариев, данных и уровней покрытия;
- использование инцидентов, логов, архитектурных изменений как входа для улучшения тестовой стратегии.
- Цель: чтобы тесты оставались живыми, релевантными и продолжали находить реальные дефекты, а не просто "подтверждать, что всё зелёное".
Вопрос 19. Есть ли минусы полной автоматизации тестирования на проекте?
Таймкод: 00:25:42
Ответ собеседника: правильный. Говорит, что полная автоматизация по сути недостижима. Отмечает минусы: высокая стоимость внедрения и поддержки, потребность в квалифицированных специалистах, сложность выбора инструментов и связанных с этим рисков, необходимость постоянной актуализации автотестов при изменении требований. Подчеркивает, что автотесты не заменяют человека и плохо покрывают аспекты вроде вёрстки.
Правильный ответ:
Идея "полностью всё автоматизировать" выглядит привлекательно, но в реальных продуктах приводит к архитектурным, организационным и экономическим проблемам. Важно понимать, почему "полная автоматизация" — не цель, а анти-паттерн.
Ключевые моменты:
- Автотесты критически нужны.
- Но они:
- не покрывают весь спектр рисков,
- стоят дорого,
- сами являются кодом с техническим долгом,
- требуют архитектуры, процессов и людей.
Основные минусы и ограничения "полной автоматизации":
- Высокая стоимость разработки и поддержки
Автотесты — это код:
- их нужно:
- спроектировать;
- написать;
- рефакторить;
- адаптировать под изменения архитектуры и требований;
- поддерживать инфраструктуру (CI, стенды, данные, контейнеры).
Риски:
- при каждом изменении API, UI, схемы БД, флоу:
- ломается пачка тестов;
- команда тратит ресурсы не на развитие продукта, а на бесконечный ремонт тестов.
Вывод:
- Авто-тесты должны быть экономически оправданы:
- автоматизируем повторяемое и критичное;
- не гонимся за формальной "100% автоматизацией".
- Хрупкость и ложное чувство безопасности
При попытке "автоматизировать всё":
- Появляется огромное количество UI/E2E тестов:
- медленных;
- нестабильных;
- чувствительных к верстке, таймингам, мелким изменениям.
- Результат:
- флаки-тесты;
- красные пайплайны по несущественным причинам;
- у команды выгорает доверие к тестам (их просто начинают игнорировать).
- При этом:
- наличие тысяч зелёных тестов создаёт иллюзию "у нас всё хорошо",
- хотя:
- тесты могут быть поверхностными,
- не покрывать реальные риски,
- проверять только "золотые пути".
Вывод:
- качество определяется не числом автотестов, а тем:
- какие риски/сценарии они покрывают,
- как они встроены в архитектуру и процессы.
- Ограниченность того, что можно адекватно автоматизировать
Есть области, где машина по определению слабее:
- UX и визуальное восприятие:
- автотест может проверить наличие элемента, но не:
- удобство,
- логичность,
- восприятие пользователем.
- автотест может проверить наличие элемента, но не:
- Контент, копирайтинг, тон коммуникации:
- ошибки смысла, культурные нюансы.
- Сложные и нестандартные сценарии поведения пользователя:
- хаотичная навигация;
- "странные" последовательности действий;
- комбинирование функций.
Часть визуальных и версточных проверок автоматизировать можно (визуальные снапшоты, сравнение скриншотов), но:
- это дорого,
- часто хрупко,
- не заменяет здравую ручную проверку ключевых экранов.
- "Полная автоматизация" плохо масштабируется при высокой изменчивости продукта
В продуктах с:
- активным A/B тестированием;
- динамичными UI;
- быстрым изменением API;
- частыми фича-флагами;
жёсткие E2E/UI автотесты:
- становятся постоянным источником боль и задержек;
- не успевают адаптироваться под изменения.
Лучший подход:
- сильный слой:
- unit-тестов (особенно для бизнес-логики в Go);
- интеграционных тестов (с БД, Kafka, очередями);
- контрактных тестов между микросервисами;
- ограниченное количество устойчивых E2E на критичные сценарии;
- точечные UI-тесты для ключевых флоу.
- Автотесты не заменяют исследовательское мышление
Автоматизация идеально подходит для:
- повторяемых регрессионных сценариев;
- проверок инвариантов;
- мониторинга ключевых контрактов и health-чеки.
Но она плохо:
- изобретает новые гипотезы о возможных дефектах;
- подмечает неочевидные несостыковки;
- анализирует нетривиальные комбинации и "странные" пути, которые выбирает живой пользователь.
Поэтому:
- исследовательское тестирование (exploratory),
- анализ логов, метрик, инцидентов,
- ревью требований и архитектуры
остаются незаменимыми.
- Технический долг в тестовой инфраструктуре
Автотесты тоже стареют:
- меняются библиотеки, версии Go, драйверы, Selenium/Cypress/Playwright, Docker/K8s;
- нужно:
- обновлять окружения,
- чинить несовместимости,
- оптимизировать время выполнения.
Если пытаться автоматизировать всё:
- тестовая инфраструктура превращается в отдельный сложный продукт,
- который требует почти той же заботы, что и основной.
Итоговый инженерный подход:
Правильный ответ на вопрос "минусы полной автоматизации" должен подвести к идее баланса.
Рациональная стратегия:
-
Автоматизировать:
- критичные бизнес-потоки (E2E, но немного и устойчиво);
- контракты между сервисами (REST/gRPC, события);
- бизнес-логику на уровне unit и сервисных тестов;
- интеграцию с БД (миграции, схемы, инварианты данных);
- smoke, регресс и health-check-и в CI/CD.
-
Оставить вручную/исследовательски:
- UX, визуальные аспекты, бренд-критичные страницы;
- новые, сырые или быстро меняющиеся фичи (до стабилизации);
- сложные сценарии, неформальные ожидания, edge-cases.
-
Постоянно:
- пересматривать, какие тесты реально дают ценность;
- удалять мертвый/дубль/бесполезный автотестовый код;
- усиливать автоматизацию там, где риск высок и сценарии стабильны.
Кратко:
Да, у "полной автоматизации" есть минусы:
- высокая стоимость и сложность поддержки;
- хрупкость и флакiness;
- ложное ощущение безопасности;
- невозможность покрыть человеческое восприятие и сложные сценарии;
- рост технического долга в самих тестах.
Поэтому цель — не "полная автоматизация", а оптимальная, риск-ориентированная автоматизация в сочетании с живым инженерным мышлением.
Вопрос 20. Назови основные HTTP-методы, используемые при работе с REST API.
Таймкод: 00:29:28
Ответ собеседника: правильный. Перечисляет GET, POST, PUT, PATCH, DELETE и дополнительно упоминает TRACE и CONNECT.
Правильный ответ:
В контексте REST основное внимание уделяется семантике методов и правильному их использованию для моделирования операций над ресурсами. Базовый набор, практически применяемый в RESTful API:
- GET
- POST
- PUT
- PATCH
- DELETE
- (также часто используются HEAD и OPTIONS)
Кратко по ключевым методам и их корректному применению.
GET
- Назначение:
- Получение представления ресурса (или коллекции ресурсов).
- Свойства:
- безопасный (safe): не должен изменять состояние сервера;
- идемпотентный: повторные вызовы не меняют результат (кроме изменений данных во времени по естественным причинам);
- кэшируемый.
- Примеры:
GET /users— список пользователей.GET /users/123— пользователь с id=123.
Go-пример:
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
user, err := h.userService.GetByID(r.Context(), id)
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "not found", http.StatusNotFound)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(user)
}
POST
- Назначение:
- Создание нового ресурса.
- Вызов операций, которые не ложатся на чистый CRUD (например, отправка команды).
- Свойства:
- не безопасен;
- не идемпотентен по семантике (повторный запрос может создать дубликаты, если специально не предусмотрена идемпотентность).
- Примеры:
POST /users— создать нового пользователя.POST /orders/123/cancel— команда на отмену заказа (если следуем command-style).
PUT
- Назначение:
- Полная замена ресурса.
- Свойства:
- идемпотентный: повторный PUT с теми же данными даёт тот же результат;
- обычно используется с конкретным ресурсом (
/resource/{id}), а не с коллекцией.
- Пример:
PUT /users/123- тело запроса содержит полное новое состояние сущности;
- поля, не указанные в теле, часто трактуются как сброшенные/удалённые.
Важно:
- Для частичного обновления лучше использовать PATCH, а не PUT, чтобы не плодить неочевидное поведение.
PATCH
- Назначение:
- Частичное обновление ресурса.
- Свойства:
- не обязан быть идемпотентным, но в хороших API его делают идемпотентным по факту;
- обновляет только указанные поля.
- Пример:
PATCH /users/123{ "email": "new@mail.com" }— меняем только email.
DELETE
- Назначение:
- Удаление ресурса.
- Свойства:
- идемпотентный: повторный DELETE того же ресурса должен давать тот же конечный результат (ресурс отсутствует), даже если ответы могут различаться (204, 404).
- Пример:
DELETE /users/123
HEAD и OPTIONS (часто используемые вспомогательные)
- HEAD:
- Как GET, но без тела ответа.
- Используется для проверки существования ресурса, получения метаданных.
- OPTIONS:
- Возвращает доступные для данного ресурса методы и настройки (часто для CORS).
TRACE и CONNECT
- Обычно не используются в публичных REST API:
- TRACE:
- отладочный, часто отключается по соображениям безопасности.
- CONNECT:
- применяется для туннелирования (например, HTTPS через прокси).
- TRACE:
- В ответе на собеседовании достаточно отметить, что основной REST-набор — это GET, POST, PUT, PATCH, DELETE (+HEAD/OPTIONS), а TRACE/CONNECT не относятся к типичным CRUD-операциям над ресурсами.
Ключевые инженерные акценты:
- Корректное использование методов позволяет:
- строить предсказуемые API;
- использовать кэширование, идемпотентность, стандартизированные клиентские библиотеки;
- упрощать интеграции между сервисами.
- Внутри микросервисной архитектуры:
- важно соблюдать семантику методов и кодов ответа;
- для критичных операций (создание платежей, заказов) — продумывать идемпотентные POST (идемпотентные ключи, уникальные request_id).
Такой уровень ответа показывает не просто знание списка методов, а понимание их семантики и влияния на дизайн REST API.
Вопрос 21. Что такое идемпотентность HTTP-методов и какие методы считаются идемпотентными?
Таймкод: 00:29:56
Ответ собеседника: неполный. Корректно описывает идею, что повторный одинаковый запрос не должен приводить к дополнительным изменениям и относит к таким методам GET и HEAD. Сомневается насчёт DELETE и в конце упрощает идемпотентность до "методы, не изменяющие состояние", что не совсем верно с точки зрения спецификации.
Правильный ответ:
Идемпотентность HTTP-метода — это свойство, при котором многократное выполнение одного и того же запроса с одинаковыми параметрами приводит к тому же итоговому состоянию сервера, что и один такой запрос.
Ключевой момент:
- Идемпотентность — не про отсутствие изменений вообще.
- Это про то, что:
- первый запрос может изменить состояние,
- но повторные идентичные запросы не должны приводить к дополнительным "накопительным" эффектам.
По HTTP-спецификации к идемпотентным методам относятся:
- GET
- HEAD
- PUT
- DELETE
- OPTIONS
- TRACE
POST — формально не идемпотентен.
Разберём на практике.
GET
- Должен только читать данные, без изменения состояния.
- Повторные GET-запросы:
- не меняют состояние сервера,
- возвращают одно и то же (если данные не изменились другими причинами).
- Пример:
GET /users/123— безопасен и идемпотентен.
HEAD
- Аналогичен GET, но без тела ответа.
- Тоже идемпотентен и безопасен.
PUT
-
Семантика: полная замена ресурса по конкретному идентификатору.
-
Идемпотентен, если реализован корректно:
- повторный PUT с тем же body оставляет ресурс в том же состоянии.
-
Пример:
PUT /users/123
{
"email": "user@example.com",
"active": true
}- Первый запрос создаст/обновит ресурс.
- Повторный — не должен менять состояние дополнительно (результат тот же).
PATCH
- Формально не гарантирован идемпотентным, но:
- можно спроектировать PATCH так, чтобы он был идемпотентным (например, "active": true всегда ставит состояние, а не "переключает").
- На уровне стандарта: не считать PATCH автоматически идемпотентным.
DELETE
- Спецификация определяет DELETE как идемпотентный:
- первый запрос удаляет ресурс,
- повторный DELETE того же ресурса:
- не должен создавать новые побочные эффекты,
- итоговое состояние: "ресурса нет".
- Ответы могут отличаться:
- первый раз: 200/202/204,
- второй раз: 404 — но состояние (ресурс удалён) одинаково.
- Итого:
- DELETE идемпотентен по состоянию, даже если вызывает изменения один раз.
OPTIONS
- Используется для получения информации о доступных методах и настройках.
- Не должен изменять состояние — идемпотентен.
TRACE
- Диагностический метод, тоже идемпотентен, но обычно отключен по соображениям безопасности.
POST
- Не идемпотентен по определению:
- каждый запрос может создавать новый ресурс или инициировать новое действие.
- Пример:
POST /ordersс одинаковым телом может создать два разных заказа.
- Но:
- в реальных системах (платежи, заказы) часто требуется идемпотентное поведение.
- Это достигается на уровне бизнес-логики, а не спецификации метода:
- через идемпотентные ключи (Idempotency-Key),
- уникальные request_id,
- проверки дубликатов в БД.
Пример идемпотентного поведения POST на уровне реализации (Go):
func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
idemKey := r.Header.Get("Idempotency-Key")
if idemKey == "" {
http.Error(w, "missing Idempotency-Key", http.StatusBadRequest)
return
}
// Проверяем, не обрабатывался ли уже этот ключ
if resp, found := h.idemStore.Get(idemKey); found {
// Возвращаем тот же результат, не создавая новый заказ
w.WriteHeader(resp.StatusCode)
w.Write(resp.Body)
return
}
// ... создаём заказ, сохраняем результат по idemKey ...
}
- Здесь:
- метод POST по стандарту не идемпотентен,
- но бизнес-логика делает конкретную операцию идемпотентной, чтобы избежать дублей при повторах.
Что важно показать на интервью:
-
Идемпотентность — это:
- свойство конечного состояния системы при повторе одинаковых запросов,
- а не просто "методы, которые ничего не меняют".
-
Формально идемпотентные методы:
- GET, HEAD, PUT, DELETE, OPTIONS, TRACE.
-
Практические нюансы:
- Корректная реализация идемпотентности — ответственность сервиса:
- DELETE не должен падать в неконсистентные состояния при повторных вызовах.
- PUT должен задавать состояние, а не "добавлять сверху".
- Для критичных операций (платежи, заказы):
- добавляются идемпотентные механизмы поверх HTTP-методов,
- чтобы повторные запросы клиента (например, при сетевых сбоях) не приводили к дублированию.
- Корректная реализация идемпотентности — ответственность сервиса:
Сильный ответ:
- точно формулирует определение,
- знает список идемпотентных методов по спецификации,
- понимает разницу между "безопасный" (safe) и "идемпотентный",
- показывает, как идемпотентность используется и реализуется в реальных REST/микросервисах.
Вопрос 22. Как передать параметры в HTTP-методе GET?
Таймкод: 00:30:51
Ответ собеседника: правильный. Указывает, что параметры передаются в URL, что соответствует стандартной практике.
Правильный ответ:
В GET-запросах параметры передаются в URL. Основные способы:
- Путь (path parameters)
Используются для идентификации конкретного ресурса.
Примеры:
/users/123/orders/2024/10/01
Обычно описываются в формате:
/resource/{id}
- Query-параметры (строка запроса)
Используются для фильтрации, сортировки, пагинации, поиска и дополнительных опций.
Синтаксис:
- после пути ставится
? - параметры в формате
key=value - несколько параметров разделяются
&
Примеры:
- фильтрация и поиск:
/users?role=admin/products?category=books&min_price=100&max_price=500
- пагинация:
/users?page=2&page_size=50
- сортировка:
/orders?sort=created_at&order=desc
Важно:
- пробелы и спецсимволы кодируются (URL-encoding):
search=iphone 15 pro→search=iphone%2015%20pro
- чувствительные данные (пароли, токены) в GET-параметрах передавать нельзя:
- они попадают в логи, историю браузера, прокси.
- Комбинация path + query
Типичный REST-подход:
- путь задаёт ресурс,
- query — опции и критерии.
Пример:
/users/123/orders?status=active&limit=20
Пример обработки query-параметров в Go:
func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request) {
// Значение параметра "role"
role := r.URL.Query().Get("role")
// Пагинация с значениями по умолчанию
pageStr := r.URL.Query().Get("page")
sizeStr := r.URL.Query().Get("page_size")
page := 1
size := 20
if pageStr != "" {
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
page = p
}
}
if sizeStr != "" {
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
size = s
}
}
users, err := h.userService.List(r.Context(), role, page, size)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(users)
}
Кратко:
- В GET параметры передаются в URL:
- идентификаторы и ресурсы — в path;
- фильтры, сортировки, пагинация, поиск — в query-параметрах.
Вопрос 23. В чем разница между методами PUT и PATCH?
Таймкод: 00:30:59
Ответ собеседника: правильный. Корректно указано, что PUT выполняет замену ресурса целиком, а PATCH применяется для частичного обновления.
Правильный ответ:
Разница между PUT и PATCH — не только в формулировке "целый объект vs частичное изменение", но и в семантике, идемпотентности и типичных практиках реализации в REST API.
Основные отличия:
- Семантика
-
PUT:
- Означает полную замену ресурса по указанному идентификатору.
- Тело запроса описывает новое состояние ресурса целиком.
- Поля, не указанные в теле (при классическом трактовании), либо сбрасываются в значения по умолчанию, либо считаются удаленными (важно явно определить в API-спецификации).
- Типичный URL:
/resource/{id}.
-
PATCH:
- Означает частичное обновление ресурса.
- Тело запроса содержит только те поля (или операции), которые нужно изменить.
- Остальные поля ресурса остаются без изменений.
- Подходит для "точечных" апдейтов.
- Идемпотентность
-
PUT:
- По спецификации должен быть идемпотентным.
- Повторный PUT с теми же данными приводит к тому же состоянию ресурса.
-
PATCH:
- Формально не обязан быть идемпотентным.
- Но хороший дизайн стремится делать PATCH-операции идемпотентными:
- "set field = value", а не "toggle" или "increment", если это не явно задокументировано.
- Если PATCH реализован как "инструкции" (JSON Patch, RFC 6902), идемпотентность зависит от набора операций.
- Типичные практики использования
-
Когда использовать PUT:
- Конфигурации и сущности с фиксированным набором полей.
- Сценарии, где клиент всегда оперирует полной моделью объекта.
- Пример:
- управление настройками сервиса:
PUT /settings- тело содержит весь набор настроек.
- управление настройками сервиса:
-
Когда использовать PATCH:
- Большие ресурсы, где передавать целиком весь объект ради одного поля неэффективно.
- Высоконагруженные API, где важно минимизировать объем данных.
- Частые частичные обновления:
- смена email;
- изменение статуса;
- обновление одного атрибута.
- Примеры реализации в Go
Пример PUT (полная замена пользователя):
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
}
func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
// Игнорируем ID из тела, доверяем URL
u.ID = id
// Логика: считаем, что клиент передает полный объект.
// Неуказанные поля могут трактоваться как очистка/дефолт.
if err := h.userService.Replace(r.Context(), u); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(u)
}
Пример PATCH (частичное обновление):
Подход 1: "partial struct"
type UpdateUserRequest struct {
Email *string `json:"email,omitempty"`
Name *string `json:"name,omitempty"`
}
func (h *Handler) PatchUser(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var req UpdateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
if err := h.userService.PartialUpdate(r.Context(), id, req); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
- Здесь:
- nil означает "поле не менять";
- непустое значение — "обновить это поле".
Подход 2: JSON Patch (операции):
[
{ "op": "replace", "path": "/email", "value": "new@example.com" },
{ "op": "remove", "path": "/name" }
]
- Более гибко, но сложнее в реализации.
- Типичные ошибки и что важно сказать на интервью
-
Ошибка: использовать PUT как частичное обновление "по факту", без явной спецификации.
- Это ломает ожидаемую семантику и идемпотентность.
-
Ошибка: использовать PATCH для "магических" операций без понятной модели (например, "немного поменять что-то внутри", не описывая контракт).
-
Хороший ответ:
- четко разделяет:
- PUT — полная замена ресурса, идемпотентен;
- PATCH — частичное обновление, семантика определяется контрактом;
- понимает влияние выбора метода на:
- клиентские реализации,
- кэширование,
- предсказуемость API,
- поддержку и эволюцию микросервисов.
- четко разделяет:
Кратко:
-
PUT:
- заменить ресурс целиком;
- идемпотентен;
- клиент обязан прислать полную модель (если не оговорено иначе).
-
PATCH:
- изменить часть ресурса;
- тело запроса содержит только изменения;
- по стандарту не гарантированно идемпотентен, но в хорошо спроектированном API — предсказуем и безопасен.
Вопрос 24. Какие существуют группы HTTP статус-кодов ответа сервера?
Таймкод: 00:31:07
Ответ собеседника: неполный. Верно перечисляет:
- 2xx — успешные;
- 3xx — редиректы;
- 4xx — клиентские ошибки;
- 5xx — серверные ошибки. Упоминает 1xx как информационные, но ошибочно называет их устаревшими и недостоверными.
Правильный ответ:
HTTP-статус-коды делятся на пять классов по первой цифре. Все пять групп актуальны и стандартизированы (включая 1xx).
Классы статус-кодов:
- 1xx — Informational (информационные)
- Означают, что запрос получен, и сервер продолжает обработку, но финальный ответ ещё не отправлен.
- Используются в специфичных сценариях для оптимизации обмена и протокольных деталей.
- Не являются устаревшими; их реже видно на уровне приложений, но они активно используются, например, в связке с
Expect: 100-continue.
Примеры:
- 100 Continue:
- Клиент отправляет заголовки и ждет подтверждения, прежде чем слать большой body.
- Сервер отвечает 100 Continue, если готов принять тело.
- 101 Switching Protocols:
- Используется при переключении протокола (например, для WebSocket).
- 103 Early Hints:
- Может быть использован для ранней отправки подсказок (link-заголовки для прилоада ресурсов).
- 2xx — Success (успешные)
Говорят, что запрос успешно обработан.
Ключевые:
- 200 OK:
- успешный запрос (GET/PUT/PATCH/DELETE и т.п.).
- 201 Created:
- ресурс успешно создан (чаще для POST).
- 202 Accepted:
- запрос принят к обработке, но не завершен (асинхронные операции).
- 204 No Content:
- успешно, но без тела ответа (например, DELETE, PUT без содержимого).
- 3xx — Redirection (перенаправление)
Указывают, что для завершения запроса клиенту нужно перейти по другому URL или выполнить дополнительный шаг.
Ключевые:
- 301 Moved Permanently:
- постоянный редирект.
- 302 Found:
- временный редирект (в исторической практике часто используется неправильно).
- 303 See Other:
- после POST/PUT перенаправляет на GET другого ресурса.
- 307 Temporary Redirect / 308 Permanent Redirect:
- более строгие варианты редиректов, сохраняющие метод и тело (для 307/308).
- 4xx — Client Error (ошибка клиента)
Проблема на стороне запроса: некорректные данные, права, формат, ресурс.
Ключевые:
- 400 Bad Request:
- некорректный запрос (валидация, формат).
- 401 Unauthorized:
- требуется аутентификация (или неверный токен).
- 403 Forbidden:
- доступ запрещен при корректной аутентификации.
- 404 Not Found:
- ресурс не найден.
- 409 Conflict:
- конфликт состояний (например, при дубликатах или версиях).
- 422 Unprocessable Entity:
- корректный по синтаксису запрос, но семантически неверен (часто для валидации).
- 5xx — Server Error (ошибка сервера)
Сервер не смог корректно обработать корректный запрос.
Ключевые:
- 500 Internal Server Error:
- общая ошибка сервера.
- 502 Bad Gateway:
- некорректный ответ от вышестоящего сервиса/прокси.
- 503 Service Unavailable:
- сервис временно недоступен (перегрузка, обслуживание).
- 504 Gateway Timeout:
- таймаут при обращении к внешнему/внутреннему сервису.
Инженерные акценты для реальных систем:
- Корректное использование кодов:
- уменьшает двусмысленность;
- упрощает клиентам и другим сервисам обработку ошибок и ретраев.
- В микросервисах:
- важно различать 4xx и 5xx:
- 4xx обычно не ретраится (ошибка запроса),
- 5xx может быть кандидатом для автоматического retry с backoff.
- важно различать 4xx и 5xx:
- Для REST API:
- 201 для успешного создания,
- 204 для успешного удаления/обновления без тела,
- 400/422 для валидации,
- 409 для конфликтов,
- 401/403 для authz/authn.
Пример использования в Go:
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
user, err := h.userService.GetByID(r.Context(), id)
if err != nil {
switch {
case errors.Is(err, ErrNotFound):
http.Error(w, "not found", http.StatusNotFound)
case errors.Is(err, ErrBadRequest):
http.Error(w, "bad request", http.StatusBadRequest)
default:
http.Error(w, "internal error", http.StatusInternalServerError)
}
return
}
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(user)
}
Кратко:
- Все 5 классов (1xx–5xx) актуальны.
- На собеседовании важно:
- правильно классифицировать,
- не считать 1xx "устаревшими",
- показывать понимание практического применения кодов для проектирования API и надежных интеграций.
Вопрос 25. Насколько уверенно ты работаешь с Postman при тестировании API?
Таймкод: 00:31:31
Ответ собеседника: правильный. Описывает умение создавать и поддерживать коллекции, использовать переменные, запускать коллекции через Runner, применять сниппеты тестов. Отмечает, что глубокого опыта продвинутой автоматизации пока нет.
Правильный ответ:
Уверенная работа с Postman для инженерной практики тестирования API — это не только ручной запуск запросов, но и системный подход: использование коллекций как спецификации, параметризация, автоматизированные проверки, интеграция с CI и командной разработкой.
Ключевые аспекты продвинутого использования Postman.
- Структурирование коллекций и окружений
- Коллекции:
- группировка запросов по доменам и микросервисам:
auth,users,orders,billing,admin. - отражение REST-структуры:
GET /users,POST /users,GET /users/{id}, и т.д.
- группировка запросов по доменам и микросервисам:
- Окружения (Environments):
local,dev,stage,prod-like.- Хранение base URL, токенов, ключей и других переменных окружения.
- Пример переменных:
{{base_url}}/api/v1/users{{access_token}}
Это позволяет:
- одним и тем же набором запросов проверять разные стенды;
- легко переключаться между окружениями.
- Переменные и chained-запросы
Уверенная работа предполагает активное использование:
- Environment / Collection variables;
- сохранение данных из ответов для последующих запросов.
Пример (Postman Tests вкладка):
// Сохранить user_id из ответа
let res = pm.response.json();
pm.collectionVariables.set("user_id", res.id);
Далее в запросе:
GET {{base_url}}/api/v1/users/{{user_id}}
Так строятся сквозные сценарии (signup → login → get profile → create order) без ручного копирования ID.
- Автотесты в Postman (Tests)
Использование Tests — обязательный элемент, а не опция:
-
Проверка статус-кодов:
pm.test("Status is 200", function () {
pm.response.to.have.status(200);
}); -
Проверка структуры и обязательных полей:
const json = pm.response.json();
pm.test("User has id and email", () => {
pm.expect(json).to.have.property("id");
pm.expect(json).to.have.property("email");
}); -
Проверка бизнес-логики:
pm.test("Amount is positive", () => {
pm.expect(json.amount).to.be.above(0);
}); -
Валидация схемы (через JSON Schema):
const schema = {
type: "object",
required: ["id", "email"],
properties: {
id: { type: "string" },
email: { type: "string", format: "email" }
}
};
pm.test("Response schema is valid", function () {
pm.expect(tv4.validate(pm.response.json(), schema)).to.be.true;
});
Это делает коллекцию не просто "набором запросов", а автоматизированным набором проверок, который можно гонять через Runner и CI.
- Postman Runner и автоматизация (Newman)
Уверенный уровень — это умение:
- Запускать коллекции в Runner:
- smoke/regression для API;
- прогон сквозных сценариев.
- Использовать Newman (CLI-раннер) для интеграции в CI/CD:
- GitLab CI, GitHub Actions, Jenkins, TeamCity и др.
Пример интеграции Newman в CI (фрагмент):
newman run collection.json \
-e environment.json \
--reporters cli,junit \
--reporter-junit-export newman-report.xml
Такой подход:
- позволяет автоматически проверять API после деплоя на стенд;
- превращает Postman-коллекции в часть регрессионного набора.
- Работа с аутентификацией и токенами
Уверенное владение включает:
- автоматическое получение и обновление токенов в Pre-request Scripts:
- OAuth2 / JWT / сервисные токены;
- отсутствие "захардкоженных" токенов в запросах и коллекциях.
Пример Pre-request (получение токена и сохранение в переменную):
if (!pm.environment.get("access_token")) {
// Пример: запрос токена (может быть вынесен в отдельный запрос + скрипт)
// В реальном пайплайне можно дернуть auth-эндпоинт и сохранить токен.
}
pm.request.headers.add({
key: "Authorization",
value: `Bearer ${pm.environment.get("access_token")}`
});
- Поддержка контрактов и работа с ошибками
- Проверка не только happy-path, но и негативных сценариев:
- 400/401/403/404/409/422;
- валидация тела ответа при ошибках (код/сообщение).
- Важно:
- фиксировать контракты (структуру ответов) тестами;
- чтобы любые breaking changes проявлялись сразу.
- Связка с backend (Go) и БД
На продвинутом уровне Postman часто используется совместно с:
- локально поднятым Go-сервисом (Docker, Kubernetes, bare metal);
- тестовой PostgreSQL / Redis / Kafka.
Сценарий:
- поднять сервис локально;
- прогнать коллекцию Postman:
- smoke;
- базовые интеграции;
- проверка CRUD и авторизации.
Это:
- ускоряет локальную разработку;
- помогает QA и разработчикам говорить на одном языке (запросы, тела, статусы).
Итого, уверенный уровень работы с Postman включает:
- продуманную структуру коллекций и окружений;
- активное использование переменных;
- цепочку запросов (end-to-end сценарии);
- написание тестов на вкладке Tests (статусы, структура, бизнес-правила);
- запуск коллекций через Runner и Newman в CI/CD;
- корректную работу с токенами и авторизацией;
- проверку позитивных и негативных сценариев.
Такой подход превращает Postman из "ручного клиента" в полноценный инструмент автоматизации и верификации API-контрактов на проекте.
Вопрос 26. Какие типы переменных есть в Postman и как они используются?
Таймкод: 00:31:44
Ответ собеседника: правильный. Упоминает локальные и глобальные переменные, описывает их область видимости и дополнительно называет переменные окружения.
Правильный ответ:
В Postman переменные позволяют переиспользовать значения, не дублировать конфигурацию и строить гибкие сценарии тестирования API. Ключевое отличие типов переменных — в области видимости и приоритете.
Основные типы переменных в Postman:
- Переменные окружения (Environment variables)
- Привязаны к конкретному окружению:
local,dev,test,stage,prod-likeи т.п.
- Применяются для значений, зависящих от стенда:
base_url- токены доступа
- креды тестовых пользователей
- Примеры:
{{base_url}} = https://api.dev.example.com{{base_url}} = https://api.stage.example.com
- Использование:
- один и тот же запрос:
GET {{base_url}}/api/v1/users
- запускается против разных стендов просто сменой окружения.
- один и тот же запрос:
- Переменные коллекции (Collection variables)
- Действуют внутри конкретной коллекции.
- Удобны для:
- общих настроек данной доменной области:
service_path,api_version,default_limit; - идентификаторов или значений, полученных в одном запросе и используемых в других внутри коллекции.
- общих настроек данной доменной области:
- Пример:
- в тесте после создания пользователя:
const res = pm.response.json();
pm.collectionVariables.set("user_id", res.id); - далее в других запросах:
GET {{base_url}}/api/v1/users/{{user_id}}
- в тесте после создания пользователя:
- Глобальные переменные (Global variables)
- Доступны во всех воркспейсах/коллекциях в рамках текущей рабочей среды Postman.
- Использовать стоит осторожно:
- удобно для временных значений во время отладки;
- но в командной работе могут создавать путаницу.
- Пример:
{{debug}} = true- или временно
{{token}}при ручной работе.
- Рекомендуется:
- не хранить секреты и критичные значения в глобальных переменных;
- не полагаться на них в командных коллекциях — лучше окружения и переменные коллекции.
- Локальные переменные (Local variables)
- Существуют в рамках одного запроса (scope запроса).
- Чаще задаются в скриптах:
let,const,varв Pre-request или Tests.
- Не пересекаются между запросами (если не сохранить явно в env/collection/global).
- Пример:
let timestamp = Date.now();
pm.environment.set("ts", timestamp); - Используются:
- для вычислений, генерации данных, подготовки значений перед сохранением в более широкую область видимости.
- Data variables (переменные из Data-файлов в Runner)
- Используются при запуске коллекций через Runner или Newman с CSV/JSON данными.
- Позволяют параметризовать запросы набором входных данных (data-driven testing).
- Пример:
-
CSV:
email,password
user1@example.com,Pass1
user2@example.com,Pass2 -
В запросе:
{{email}},{{password}}— берутся из текущей строки набора данных.
-
- Переменные в скриптах (pm.variables)
- Есть единый интерфейс разрешения переменных с учетом приоритета.
- Приоритет (от более специфичного к более общему):
- локальная → data → окружение → коллекция → глобальная.
- В JS-скриптах можно обращаться к значению с учетом этого порядка:
const value = pm.variables.get("name");
Практические рекомендации:
environment:- для base_url, токенов, конфигурации для конкретного стенда.
collection:- для общих значений в рамках одной коллекции/домена.
data:- для массовых прогонов с разными наборами данных.
local (JS-переменные):- для временных вычислений внутри запроса.
global:- минимально, только для временных/локальных задач.
Грамотное использование разных уровней переменных:
- упрощает поддержку коллекций;
- позволяет одной и той же коллекцией покрывать несколько окружений;
- делает Postman-коллекции удобным инструментом для smоke/regression/API-тестов и для интеграции с CI (через Newman).
Вопрос 27. Какие способы авторизации запросов ты знаешь и как применять их в Postman?
Таймкод: 00:32:22
Ответ собеседника: неполный. Упоминает Bearer-токены в заголовке Authorization, сессионные ID, JWT, OAuth2 и OpenID, но частично смешивает уровни и не структурирует варианты именно как стандартные схемы в Postman (Basic, Bearer, API Key и т.д.).
Правильный ответ:
При работе с API важно чётко различать:
- схемы аутентификации/авторизации (как клиент подтверждает, кто он и что ему можно);
- формат и носитель токена (JWT или нет);
- протокол/флоу получения токена (OAuth2, OpenID Connect);
- конкретную реализацию в инструментах вроде Postman.
Основные используемые схемы и их применение в Postman.
Основные схемы авторизации для HTTP/API:
- Basic Auth
Суть:
- Логин и пароль кодируются в Base64 и передаются в заголовке:
Authorization: Basic base64(username:password)
Особенности:
- Используется для простых сервисов, внутренних API, технических учёток.
- Обязательно только по HTTPS (иначе credentials утекут).
В Postman:
- Во вкладке Authorization выбираем:
- Type: Basic Auth
- Вводим Username / Password.
- Postman сам сформирует заголовок.
- Bearer Token
Суть:
- В заголовке передается токен доступа:
Authorization: Bearer <token>
- Токен может быть:
- произвольной строки (opaque token),
- JWT (JSON Web Token) — просто формат токена, а не отдельный метод авторизации.
Использование:
- Наиболее распространённый вариант в современных REST/микросервисах.
- Токен обычно выдается:
- отдельным auth-сервисом;
- по OAuth2/OpenID Connect флоу;
- или через собственный login endpoint.
В Postman:
- Способы:
- Authorization → Type: Bearer Token → вставить токен.
- Хранить токен в переменной:
Authorization: Bearer {{access_token}}- токен получать/обновлять в Pre-request Script или отдельным запросом.
- API Key
Суть:
- Статический ключ, привязанный к приложению/аккаунту.
- Может передаваться:
- в заголовке:
X-API-Key: <key>
- или в query-параметре:
?api_key=<key>
- в заголовке:
Использование:
- Публичные / партнёрские API;
- Сервис-сервис интеграции;
- Ограничение прав и rate limit по ключу.
В Postman:
- Authorization → Type: API Key
- Key: имя заголовка или параметра (например,
X-API-Key) - Value:
{{api_key}} - Add to: Header или Query params.
- Key: имя заголовка или параметра (например,
- OAuth 2.0
Суть:
- Стандарт протокола авторизации:
- как клиент получает токен доступа от авторизационного сервера.
- Основные флоу:
- Authorization Code (PKCE) — для веб/мобильных клиентов;
- Client Credentials — для сервер-сервер интеграций;
- Refresh Token — обновление токенов.
Важно:
- OAuth2 — это про получение токена.
- Доступ к ресурсам часто через Bearer-токен:
Authorization: Bearer <access_token>.
В Postman:
- Authorization → Type: OAuth 2.0
- Настраиваем:
- Grant Type (Client Credentials, Auth Code и т.д.);
- URL авторизации, URL токен-эндпоинта;
- client_id / client_secret;
- scopes.
- Нажимаем Get New Access Token → Postman получает токен.
- Use Token → Postman автоматом добавляет Bearer в запросы.
- Настраиваем:
- OpenID Connect (OIDC)
Суть:
- Надстройка над OAuth2 для аутентификации.
- Возвращает:
- ID Token (часто JWT) с информацией о пользователе.
- Используется для:
- SSO;
- федеративной аутентификации.
В контексте API:
- ID Token обычно не используется для доступа к ресурсам,
- для этого есть Access Token (часто тоже Bearer).
- На уровне Postman:
- работа схожа с OAuth2 (получение токена и подстановка в заголовок).
- Cookie-based / Session ID
Суть:
- Классический веб-подход:
- после логина сервер выдает cookie с session id;
- браузер автоматически отправляет cookie дальше.
- В API:
- иногда используется в админках и внутренних UI.
В Postman:
- Либо логин-запрос, который устанавливает cookie;
- Либо ручная настройка cookie во вкладке Cookies.
- Далее Postman будет слать cookie в последующих запросах.
- Custom token / HMAC / подписи запросов
Суть:
- Свои схемы:
- HMAC-подписи:
- заголовки вида
X-Signature,X-Timestamp,X-Client-Id;
- заголовки вида
- Signed URL;
- комбинации ключ + подпись тела запроса.
- HMAC-подписи:
Использование:
- Финтех, интеграции с внешними партнерами, повышенные требования безопасности.
В Postman:
- Генерация подписи в Pre-request Script:
- на JS считаем HMAC/sha256 и устанавливаем заголовок.
Пример Pre-request Script (набросок):
const apiKey = pm.environment.get("api_key");
const secret = pm.environment.get("api_secret");
const timestamp = Date.now().toString();
const payload = timestamp + pm.request.url.getPath() + pm.request.body.raw;
const signature = CryptoJS.HmacSHA256(payload, secret).toString();
pm.request.headers.add({ key: "X-API-Key", value: apiKey });
pm.request.headers.add({ key: "X-Signature", value: signature });
pm.request.headers.add({ key: "X-Timestamp", value: timestamp });
Ключевые инженерные акценты:
- JWT — это формат токена (JSON Web Token), а не отдельный метод авторизации:
- может использоваться в Bearer-схеме:
Authorization: Bearer <jwt>.
- может использоваться в Bearer-схеме:
- OAuth2/OpenID Connect — это протоколы получения/верификации токенов, а не сами заголовки.
- В Postman важно:
- не хардкодить токены в каждом запросе;
- использовать переменные окружения;
- автоматизировать получение токена (Pre-request Scripts, OAuth2 helper);
- выбирать тип Authorizaton на уровне коллекции, а не дублировать в каждом запросе.
Хороший ответ на интервью:
- четко называет основные схемы:
- Basic, Bearer (в т.ч. JWT), API Key, OAuth2, cookie/session, кастомные подписи;
- понимает разницу между схемой, токеном и протоколом;
- объясняет, как это настраивается в Postman:
- через вкладку Authorization,
- через переменные,
- через pre-request скрипты для автоматизации.
Вопрос 28. Как использовать DevTools при тестировании веб-интерфейса?
Таймкод: 00:33:43
Ответ собеседника: правильный. Описывает активное использование Elements, Network и Console: просмотр и изменение DOM и стилей, проверка верстки и адаптивности, анализ логов и ошибок, выполнение команд, снятие скриншотов, эмуляция устройств и сети, подмена запросов/ответов и данных для проверки валидации фронта и бэка.
Правильный ответ:
Инструменты DevTools — обязательный инструмент при тестировании веб-клиентов и фронтенда во взаимодействии с backend/API. Зрелое использование DevTools позволяет:
- точно локализовать дефекты между фронтом и бэком;
- проверять корректность запросов/ответов без "угадываний";
- выявлять проблемы в верстке, производительности и безопасности;
- воспроизводить и документировать ошибки так, чтобы backend-разработчики могли быстро их устранить.
Ключевые вкладки и практики.
- Elements (DOM + стили)
Для чего используется:
- Проверка:
- корректной разметки (семантика, вложенность, уникальность id);
- применения CSS-классов;
- адаптивности (responsive) под разные разрешения;
- корректности отображения скрытых/динамических элементов.
- Поиск проблем:
- перекрытие элементов;
- некликабельные кнопки;
- элементы вне видимой области;
- некорректная работа :hover/:active/:focus.
Практика:
- Инспекция "невидимых" или перекрытых элементов:
- если по UI "кнопка не нажимается", в Elements проверяем:
- кто сверху;
- есть ли
pointer-events: none;; - z-index, позиционирование.
- если по UI "кнопка не нажимается", в Elements проверяем:
- Проверка адаптивности:
- через Device Toolbar:
- разные устройства/ширины;
- ориентация экрана;
- retina/non-retina.
- через Device Toolbar:
- Network
Ключевая вкладка для тестирования интеграции фронт ↔ backend.
Используем для:
- Анализа всех HTTP-запросов:
- метод (GET/POST/PUT/PATCH/DELETE);
- URL и query-параметры;
- заголовки (Authorization, Content-Type, CORS);
- тело запросов (JSON, form-data);
- коды ответов (2xx/3xx/4xx/5xx);
- тело ответов (данные, ошибки).
- Проверки:
- соответствия API-спецификации (контракта);
- корректности обработки ошибок (например, backend возвращает 400/422, а фронт показывает понятное сообщение);
- CORS-проблем;
- кеширования (Cache-Control, ETag).
- Поиска проблем:
- лишние запросы;
- дубликаты;
- очень тяжелые ответы;
- долгие запросы (latency, backend-производительность).
Практический пример:
- Баг: "При сохранении формы UI показывает 'Ошибка сервера', но разработчики говорят, что всё нормально".
- Действия в Network:
- смотрим запрос:
- статус 400 или 422,
- тело:
{ "code": "INVALID_FIELD", "message": "phone is invalid" }
- вывод:
- backend работает корректно;
- фронт неправильно обрабатывает/отображает ошибку.
- смотрим запрос:
- В баг-репорт:
- точный запрос/ответ,
- скрин Network,
- это экономит десятки минут споров.
- Console
Используем для:
- Анализа ошибок JavaScript:
- исключения, stack trace;
- ошибки инициализации компонентов;
- CORS, CSP, mixed content.
- Предупреждений:
- deprecated API;
- проблемы с безопасностью.
- Ручного выполнения JS:
- проверка наличия глобальных объектов и переменных;
- быстрая диагностика состояния приложения (например, текущее состояние хранилища).
Практика:
- Если UI "не реагирует":
- первым делом смотрим Console:
- часто там красный stack trace, указывающий прямо в проблемное место.
- первым делом смотрим Console:
- Для backend-разработчиков:
- передаём в задачу:
- текст ошибки из Console,
- контекст (шаги воспроизведения),
- скрин.
- передаём в задачу:
- Application / Storage
Ключевая вкладка для:
- Cookies:
- проверка:
- установки/пролонгации сессий;
- флагов Secure, HttpOnly, SameSite.
- проверка:
- LocalStorage / SessionStorage:
- проверка:
- хранения токенов (важно: небезопасно хранить чувствительные данные);
- кэша фронта;
- проверка:
- IndexedDB:
- оффлайн-данные, кеширование.
- Cache / Service Workers:
- PWA-сценарии;
- отладка проблем из-за закешированного фронта.
Практика:
- Проверка безопасности:
- JWT или access-токен в localStorage без флагов — повод для security-замечания.
- Диагностика:
- "помогает только очистка кэша" → смотрим Service Worker, кеш, localStorage.
- Network Conditions, Throttling и эмуляция устройств
Используем для:
- Эмуляции:
- медленного 3G, высокой латентности;
- offline-сценариев;
- Проверки:
- как UI ведёт себя при плохой сети:
- есть ли лоадеры/спиннеры;
- корректные сообщения об ошибках;
- нет ли бесконечной "висящей" загрузки.
- как UI ведёт себя при плохой сети:
Практический пример:
- Тестируем:
- "поведение при таймауте backend".
- Действия:
- включаем "Slow 3G" или вручную увеличиваем latency;
- проверяем:
- корректный показ ошибки;
- отсутствие зависших UI-состояний.
- Подмена, инспекция и воспроизведение запросов
DevTools позволяют:
- копировать запрос как curl:
- удобно для backend-диагностики и автотестов;
- редактировать и повторно отправлять запрос (в некоторых браузерах или через дополнительные инструменты);
- использовать Breakpoints в XHR/fetch (через Sources) для сложной отладки.
Это помогает:
- воспроизводить баги без кликов в UI;
- переносить сценарии в Postman или автотесты;
- чётко фиксировать контрактные проблемы.
- Performance / Lighthouse / Security (более продвинутый уровень)
Для зрелого подхода:
- Performance:
- измерение времени загрузки, рендеринга, долгих JS-операций.
- Lighthouse:
- базовая оценка:
- производительности;
- accessibility;
- SEO;
- best practices.
- базовая оценка:
- Это важно для продуктов с высокими требованиями к UX и скорости.
Итоговые акценты для сильного ответа:
- DevTools — это:
- не "просто посмотреть разметку",
- а системный инструмент:
- для разделения багов фронта/бэка;
- проверки корректности API-вызовов;
- анализа ошибок JS;
- проверки безопасности хранения токенов;
- тестирования поведения при сетевых проблемах и адаптивности.
- Умение грамотно использовать Elements, Network, Console, Application/Storage, Throttling:
- ускоряет диагностику;
- повышает качество баг-репортов;
- формирует общий технический язык с frontend и backend-разработчиками.
Вопрос 29. Как использовать DevTools, в частности вкладку Network, для анализа запросов и данных приложения?
Таймкод: 00:35:55
Ответ собеседника: правильный. Уточняет, что во вкладке Network анализирует тело и заголовки запросов и ответов, а во вкладке Application проверяет Local Storage и Cookies. Это соответствует корректной практической работе с инструментами.
Правильный ответ:
Вкладка Network в DevTools — один из ключевых инструментов для анализа работы веб-приложения и его взаимодействия с backend/API. Грамотное использование Network позволяет:
- точно локализовать источник проблем (фронт vs бэк);
- проверять корректность контрактов API;
- диагностировать ошибки авторизации, валидации, CORS, кеширования;
- воспроизводить сценарии для backend-разработчиков и автотестов.
Основные аспекты использования Network.
- Отслеживание всех запросов приложения
- При открытой вкладке Network фиксируются:
- XHR/fetch запросы (основные для SPA и API);
- document (загрузка страниц);
- JS/CSS ресурсы;
- WebSocket, EventSource;
- изображения, шрифты и др.
- Можно фильтровать по типам:
- XHR/Fetch — для REST/gRPC-over-HTTP/GraphQL;
- JS/CSS — для диагностики загрузки ресурсов.
Практика:
- Воспроизводим баг → смотрим список запросов:
- были ли вызваны нужные эндпоинты,
- нет ли лишних/дублирующихся запросов.
- Анализ запросов: метод, URL, параметры
Для каждого запроса важно проверить:
- HTTP-метод:
- соответствует ли контракту (GET/POST/PUT/PATCH/DELETE);
- URL:
- правильность пути и query-параметров;
- соответствие REST-семантике и спецификации API.
Примеры:
- Проверка фильтрации:
GET /api/v1/orders?status=active&limit=50
- Проверка пагинации:
- корректная передача
page,page_size.
- корректная передача
Если UI ведет себя странно:
- проверяем, корректно ли формируются запросы (часто проблема именно на фронте).
- Заголовки запросов (Request Headers)
Особое внимание:
- Authorization:
- формат:
Authorization: Bearer <token>Basic ...- наличие/отсутствие токена.
- формат:
- Content-Type:
application/json,multipart/form-data,application/x-www-form-urlencoded.- Несоответствие Content-Type и тела часто ломает бэкенд.
- Accept:
- ожидаемые форматы ответа.
- Origin / Referer / CORS-заголовки:
- диагностика CORS-проблем.
Практика:
- Баги авторизации:
- в Network видно отсутствие/просроченный токен/не тот формат.
- Баги валидации:
- некорректный Content-Type или структура body.
- Тело запроса (Request Payload)
Для POST/PUT/PATCH важно:
- структура JSON;
- обязательные поля;
- типы значений;
- корректность кодировки.
Примеры проверок:
- UI отправляет:
- пустые поля вместо null;
- строки вместо чисел;
- лишние/устаревшие поля.
- Это помогает:
- четко показать, что проблема на фронте (формирует неверный payload),
- или наоборот — что backend некорректно обрабатывает корректный payload.
- Анализ ответов (Response)
Ключевые вещи:
- Статус-код:
- 2xx — успех;
- 4xx — клиентская ошибка (валидация, права, формат);
- 5xx — ошибка сервера.
- Тело ответа:
- сообщение об ошибке (code, message, details);
- структура данных:
- соответствие контракту (наличие полей, типы);
- нет ли лишних/секретных данных.
- Заголовки ответа:
- Cache-Control;
- CORS (Access-Control-Allow-Origin и др.);
- Content-Type;
- security-заголовки (X-Content-Type-Options, Content-Security-Policy и т.п.).
Практика:
- UI показывает "Неизвестная ошибка":
- в Network видно
400с понятным JSON:{"code":"INVALID_EMAIL"}:- фронт не отображает нормальное сообщение — баг фронта.
- в Network видно
- При 5xx:
- фиксируем точный ответ и условия — явный кандидата на баг backend.
- Вкладка Timing и производительность
Для каждого запроса:
- DNS, TCP, SSL, TTFB (time to first byte), content download.
- Помогает:
- отличить сетевые проблемы от проблем backend;
- выявить медленные эндпоинты.
Практика:
- Если UI "тормозит":
- смотрим:
- один запрос 2–5 секунд → кандидаты на оптимизацию в backend/БД;
- много лишних запросов → проблема дизайна фронта.
- смотрим:
- Использование Application для проверки сохраненных данных
В связке с Network:
- Cookies:
- токены сессий;
- флаги безопасности (HttpOnly, Secure, SameSite).
- Local Storage / Session Storage:
- хранение токенов, флагов, конфигураций.
- Проверяем:
- корректно ли обновляются токены после логина/логаута;
- нет ли утечек/небезопасного хранения.
- Практический подход при тестировании и баг-репортах
Хороший сценарий использования DevTools + Network:
- Воспроизвел баг.
- В Network:
- выделил проблемный запрос.
- Проверил:
- URL, метод;
- заголовки (особенно Authorization);
- тело запроса;
- статус и тело ответа.
- В Application:
- проверил токены, cookies, storage.
- В баг-репорт:
- приложил:
- скрин Network/Response,
- curl (Copy → Copy as cURL),
- описание ожиданий vs факта.
- приложил:
Это:
- дает backend-разработчику точный и воспроизводимый кейс;
- резко снижает время на выяснение "кто виноват".
Кратко:
- Network — основной инструмент для анализа запросов/ответов, статусов, заголовков, тел, производительности.
- Application/Storage — для анализа cookies, токенов, localStorage и поведения аутентификации.
- Грамотное использование этих инструментов:
- позволяет четко разделять фронтовые и бэковские дефекты;
- помогает валидировать API-контракты;
- делает обратную связь от тестирования технически содержательной и полезной.
Вопрос 30. Как локализовать проблему при ошибке авторизации, если логин и пароль в базе корректны?
Таймкод: 00:36:13
Ответ собеседника: правильный. Предлагает:
- проверить через DevTools/Network отправку запроса, корректность данных и код ответа;
- убедиться, что запрос вообще уходит;
- очистить cookies и кеш, повторить попытку, попробовать другой браузер;
- затем проверить авторизацию напрямую через Postman;
- на основе результатов определить, проблема на фронте или на бэке. Подход последовательный и методически верный.
Правильный ответ:
Правильная локализация проблемы авторизации — это системный процесс исключения. Цель — быстро понять, где источник ошибки:
- фронтенд (форма, запрос, обработка ответа);
- backend (логика аутентификации, БД, токены);
- окружение (cookies, кеш, CORS, конфигурация);
- интеграции (SSO, OAuth2, внешние провайдеры).
Ниже — последовательный инженерный алгоритм диагностики.
- Проверка запроса на фронте (DevTools → Network)
Сначала убеждаемся, что фронт корректно формирует и отправляет запрос.
Проверяем:
- Уходит ли запрос при нажатии "Войти":
- если нет:
- проблема во фронте: JS-ошибка, неправильный handler, отключенная кнопка, валидация не пускает запрос.
- если нет:
- Метод и URL:
- совпадает ли с реальным API-эндпоинтом авторизации:
POST /api/v1/auth/login(пример);- нет ли опечаток, лишних слэшей, неправильного пути.
- совпадает ли с реальным API-эндпоинтом авторизации:
- Тело запроса (Request Payload):
- поля и их имена:
- ожидается
{"login": "...", "password": "..."}, а не{"username": "...", "pass": "..."}и т.п.;
- ожидается
- формат:
- JSON vs form-data vs x-www-form-urlencoded;
- кодировка (UTF-8).
- поля и их имена:
- Заголовки:
Content-Type: application/json(если JSON);- отсутствие странных/лишних заголовков, ломающих backend.
- Ответ:
- статус-код (401, 403, 400, 500);
- тело ошибки:
- код/сообщение, дающие подсказку.
Если запрос не совпадает с контрактом — это баг фронта, даже если логин/пароль в базе корректны.
- Очистка локального состояния (cookies, storage, кеш)
Частая причина проблем — "грязное" состояние:
- протухшие/битые cookies;
- старые токены в localStorage/SessionStorage;
- конфликт старой версии фронта из кеша.
Действия:
- во вкладке Application:
- удалить cookies для домена;
- очистить LocalStorage/SessionStorage;
- во вкладке Network:
- отключить кэш и повторить запрос;
- проверить в режиме инкогнито / другом браузере.
Если после очистки авторизация работает:
- вероятна проблема с:
- некорректной обработкой старых сессий;
- миграцией токенов;
- кешированием фронта.
- Проверка backend-а через Postman / curl
Следующий шаг — исключить влияние фронта.
- Делаем запрос напрямую к auth-эндпоинту:
Пример:
POST https://api.example.com/api/v1/auth/login
Content-Type: application/json
{
"login": "user@example.com",
"password": "CorrectP@ssw0rd"
}
Анализируем:
- Если через Postman запрос с теми же данными:
- возвращает токен (200/201) → backend работает, проблема на фронте.
- возвращает 401/403/400 → проблема либо в данных, либо в логике backend-а, либо в окружении.
Важно:
- проверяем, что в Postman используем те же:
- URL и метод;
- тело запроса (имена полей, формат);
- заголовки;
- окружение (dev/stage/prod).
Если:
-
В Postman авторизация успешна, а через UI нет:
- фронт неправильно формирует запрос;
- некорректно обрабатывает ответ;
- подмешивает лишние заголовки/cookies;
- работает не с тем окружением (другой base_url).
-
В Postman тоже ошибка:
- идем на сторону backend.
- Диагностика на backend (если Postman тоже падает)
Дальнейшие шаги — уже совместно с backend/DevOps, но важно понимать логику.
Проверяем:
- Логи auth-сервиса:
- пришел ли запрос;
- как он интерпретирован;
- есть ли сообщения:
- "user not found";
- "invalid password";
- ошибки БД;
- проблемы с зависимыми сервисами (LDAP, OAuth, SSO).
- Базу данных:
SELECT id, login, password_hash, is_active
FROM users
WHERE login = 'user@example.com';
Смотрим:
- есть ли пользователь;
- не заблокирован ли;
- не отличается ли поле логина по регистру/формату;
- корректны ли правила шифрования/хеширования.
Типичные причины:
- смена алгоритма хеширования паролей без миграции;
- различия в trim/normalization (пробелы, регистр);
- ожидается email, а в базе логин по другому полю;
- пользователь в "inactive"/"blocked" статусе;
- разные окружения: UI смотрит на stage, а проверка логина — в prod.
- Проверка окружения и конфигурации
Важно убедиться:
- что UI и backend смотрят на одно окружение:
- часто фронт настроен на
api-dev, а вы проверяете пользователей изapi-stage;
- часто фронт настроен на
- что нет конфликтов доменов и CORS:
- не режутся ли запросы прокси, шлюзом, WAF;
- что auth-сервис корректно настроен:
- секреты (JWT secret, OAuth client_id/secret);
- URL внешних identity-провайдеров.
- Принципиальный подход (как должен звучать сильный ответ)
Последовательная стратегия:
-
- Проверяю во вкладке Network:
- уходит ли запрос,
- корректны ли URL, метод, заголовки, тело,
- что возвращает сервер (статус/ответ).
-
- Очищаю локальное состояние:
- cookies, storage, кеш, пробую в другом браузере.
-
- Проверяю backend напрямую:
- через Postman/curl — тот же запрос, те же данные.
-
- Если в Postman ок, а в UI нет:
- баг на фронте:
- неверный контракт,
- неверная обработка ответа,
- лишние данные/заголовки.
-
- Если и через Postman ошибка:
- иду в сторону backend:
- логи, БД, конфиги, внешние auth-сервисы.
-
- Документирую:
- конкретный запрос/ответ,
- окружение,
- условия воспроизведения,
- чтобы разработчики могли быстро воспроизвести.
Такой подход демонстрирует умение не "гадать", а инженерно локализовывать проблему между слоями системы, используя DevTools, Postman, логи и знание архитектуры.
Вопрос 31. Какие виды JOIN в SQL существуют и как они работают?
Таймкод: 00:37:56
Ответ собеседника: неполный. Перечисляет INNER JOIN, LEFT/RIGHT JOIN, FULL JOIN, CROSS JOIN, SELF JOIN и OUTER JOIN. В целом правильно объясняет:
- INNER/LEFT/RIGHT/FULL — как соединения по ключам с выводом несопоставленных строк через NULL,
- CROSS JOIN — как декартово произведение,
- SELF JOIN — как соединение таблицы с самой собой. Однако путаница при объяснении OUTER JOIN и визуальных схем (пересечения кругов) делает часть ответа неточной.
Правильный ответ:
JOIN — это операция, которая позволяет объединять строки из двух (или более) таблиц по определенному условию. Важно чётко понимать:
- какие строки попадают в результат;
- откуда берутся NULL;
- чем отличаются типы JOIN;
- как это влияет на корректность данных и производительность.
Рассмотрим основные виды JOIN с акцентом на точное поведение.
Условные примеры таблиц:
-- Пользователи
users (id, name)
1, 'Alice'
2, 'Bob'
3, 'Charlie'
-- Заказы
orders (id, user_id, amount)
10, 1, 100
11, 1, 200
12, 2, 150
Если у пользователя нет заказов, он в orders не фигурирует. Если есть "битый" заказ с user_id, которого нет в users — это отдельный кейс.
- INNER JOIN
Возвращает только те строки, для которых условие соединения выполняется с обеих сторон.
Пример:
SELECT u.id, u.name, o.id AS order_id, o.amount
FROM users u
INNER JOIN orders o ON o.user_id = u.id;
Результат:
- Alice — 2 строки (для двух заказов),
- Bob — 1 строка,
- Charlie — не попадет, если нет заказов,
- заказы с несуществующим user_id не попадут.
Смысл:
- "Пересечение множеств" по условию.
- LEFT JOIN (LEFT OUTER JOIN)
Возвращает все строки из левой таблицы + совпадения из правой. Если совпадений нет — колонки правой таблицы будут NULL.
SELECT u.id, u.name, o.id AS order_id, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;
Результат:
- Alice — ее заказы;
- Bob — его заказ;
- Charlie — строка с order_id = NULL, amount = NULL (нет заказов).
Смысл:
- "Все слева, справа только совпавшие".
- Очень полезно, когда:
- нужно показать всех пользователей, даже без заказов;
- искать "без связанной сущности" через
WHERE o.id IS NULL.
- RIGHT JOIN (RIGHT OUTER JOIN)
Симметричен LEFT JOIN, но с приоритетом правой таблицы.
SELECT u.id, u.name, o.id AS order_id, o.amount
FROM users u
RIGHT JOIN orders o ON o.user_id = u.id;
Возвращает:
- все заказы (правая таблица),
- пользователей — только если они существуют.
- Если есть заказ с user_id, которого нет в users:
- u.id и u.name будут NULL.
На практике:
- во многих проектах почти не используется;
- вместо RIGHT JOIN часто перестраивают запрос на LEFT JOIN, поменяв местами таблицы для читаемости.
- FULL OUTER JOIN (FULL JOIN)
Возвращает:
- все строки, которые имеют совпадения (как INNER);
- все строки только из левой таблицы (как LEFT);
- все строки только из правой таблицы (как RIGHT).
Несовпадающие колонки с другой стороны — NULL.
SELECT u.id, u.name, o.id AS order_id, o.amount
FROM users u
FULL OUTER JOIN orders o ON o.user_id = u.id;
Результат:
- все комбинации user–order, где есть совпадение;
- пользователи без заказов;
- заказы без валидного user_id.
Используется:
- меньше, чем LEFT/INNER;
- полезен при анализе несогласованных данных, миграциях, аудите.
Важно:
- "OUTER JOIN" — это семейство (LEFT/RIGHT/FULL OUTER).
- Просто "OUTER JOIN" без LEFT/RIGHT/FULL обычно не используется: нужно указывать вид.
- CROSS JOIN
Декартово произведение: каждая строка левой таблицы сочетается с каждой строкой правой.
SELECT u.name, o.id AS order_id
FROM users u
CROSS JOIN orders o;
Если:
- 3 users и 3 orders → 9 строк.
Применение:
- генерация комбинаций;
- редко в бизнес-логике, чаще в спец задачах или случайно (забыли ON в JOIN).
Опасность:
- может резко взорвать размер результата, если применен по ошибке.
- SELF JOIN
Не отдельный тип в синтаксисе, а приём: таблица соединяется сама с собой.
Пример: users с полем manager_id:
employees (id, name, manager_id)
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
Применение:
- иерархии;
- связи внутри одной сущности (родитель–потомок).
- Важные нюансы и типичные ошибки
-
Условие соединения (ON) нужно указывать явно (кроме осознанного CROSS JOIN):
- Отсутствие ON при обычном JOIN → фактически CROSS JOIN + фильтрация в WHERE, что может привести к огромным промежуточным таблицам.
-
Фильтрация vs LEFT JOIN:
- типичная ошибка — использовать условия по правой таблице в WHERE вместо ON.
Пример:
Плохо (ломает LEFT JOIN, превращая его в INNER):
SELECT u.id, u.name, o.id AS order_id
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NOT NULL;
- Здесь мы отсекаем строки без заказов в WHERE, теряя смысл LEFT.
Правильно:
- если хотим только тех, у кого есть заказы — можно сразу INNER JOIN;
- если хотим всех, включая без заказов:
- фильтрующие условия по правой таблице должны быть в ON или учитывать NULL.
Пример поиска пользователей без заказов:
SELECT u.id, u.name
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;
- Здесь логика верна: мы специально ищем строки, где связанного заказа нет.
- Производительность и практика
-
INNER JOIN:
- основной рабочий инструмент;
- хорошо поддерживается индексами.
-
LEFT JOIN:
- для выборок "все из основной таблицы + опциональные данные";
- внимательно к фильтрам (избегать случайного превращения в INNER).
-
FULL OUTER JOIN:
- поддерживается не во всех движках (например, в чистом MySQL до 8.0 нет нативного FULL JOIN);
- может заменяться UNION LEFT+RIGHT.
-
CROSS JOIN:
- использовать только осознанно.
Грамотный ответ на интервью должен:
- четко разделять:
- INNER — пересечение,
- LEFT/RIGHT — "все слева/справа + совпадения",
- FULL — объединение с сохранением всех строк,
- CROSS — декартово произведение,
- SELF — приём, а не новый тип;
- понимать, откуда берутся NULL в результатах;
- не путать OUTER JOIN как общее слово с конкретными LEFT/RIGHT/FULL;
- показывать практическое мышление: поиск строк без связей, анализ "битых" связей, влияние условий WHERE на поведение LEFT/RIGHT JOIN.
Вопрос 32. Как вывести только уникальные значения в SQL-запросе?
Таймкод: 00:40:36
Ответ собеседника: правильный. Указывает, что для получения уникальных значений используется DISTINCT и аналогичного эффекта можно добиться с помощью GROUP BY.
Правильный ответ:
Для получения уникальных значений в SQL основным и семантически явным инструментом является DISTINCT. В ряде случаев того же результата можно добиться с помощью GROUP BY, но важно понимать различия по смыслу и читаемости.
- DISTINCT — прямой способ выбрать уникальные строки
- DISTINCT применяется ко всей выборке полей, указанных в SELECT.
- Удаляет дубликаты строк, оставляя только уникальные комбинации значений.
Примеры:
- Уникальные значения одного поля:
SELECT DISTINCT status
FROM orders;
Вернет каждый статус (например: new, paid, canceled) один раз, даже если в таблице тысячи строк с одинаковым статусом.
- Уникальные комбинации полей:
SELECT DISTINCT user_id, status
FROM orders;
Вернет пары (user_id, status) без повторов:
- если у пользователя много заказов со статусом paid, пара (user_id, paid) будет только одна.
- GROUP BY как альтернативный способ
GROUP BY группирует строки по указанным полям. Если нужно только уникальные значения — можно использовать:
SELECT status
FROM orders
GROUP BY status;
Это вернет тот же результат, что и:
SELECT DISTINCT status
FROM orders;
Но есть нюансы:
- DISTINCT:
- более декларативен: явно показывает, что нужны уникальные значения;
- лучше читается, когда нет агрегатных функций.
- GROUP BY:
- логичен, когда кроме уникальности нужно считать агрегаты:
- COUNT, SUM, AVG и т.п.
- логичен, когда кроме уникальности нужно считать агрегаты:
Пример с агрегатами:
SELECT status, COUNT(*) AS cnt
FROM orders
GROUP BY status;
Здесь DISTINCT не подходит, потому что нам нужны и уникальные статусы, и агрегированные данные.
- Практические моменты и рекомендации
-
Использовать DISTINCT:
- когда цель — убрать дубликаты и получить уникальный набор значений/комбинаций;
- особенно в подзапросах:
-
например, список уникальных пользователей, совершавших заказы:
SELECT DISTINCT user_id
FROM orders;
-
-
Использовать GROUP BY:
- когда одновременно с уникальностью требуется агрегация;
- либо в редких случаях, когда в конкретной команде принят такой стиль (но это спорно, DISTINCT более выразителен).
- Подводные камни
- DISTINCT применяется ко всем выбранным полям, а не к одному:
SELECT DISTINCT user_id, created_at
FROM orders;
- Здесь уникальность будет по паре (user_id, created_at).
- Если нужен "уникальный user_id", а created_at при этом разный — DISTINCT не "схлопнет" строки. Для таких задач:
- либо DISTINCT только по user_id в подзапросе;
- либо агрегирующие функции (MIN/MAX created_at).
Пример:
SELECT DISTINCT user_id
FROM orders;
или
SELECT user_id, MIN(created_at) AS first_order_at
FROM orders
GROUP BY user_id;
- Использование в контексте приложений и Go-кода
Типичный пример из backend-сервиса на Go:
rows, err := db.QueryContext(ctx, `
SELECT DISTINCT status
FROM orders
`)
if err != nil {
// handle error
}
defer rows.Close()
- Такой запрос уменьшает объем данных и перенос логики уникализации из приложения в БД, где это делается эффективнее.
Кратко:
- Основной и правильный способ получить уникальные значения: DISTINCT.
- GROUP BY дает тот же эффект только при выборе группируемых полей, но семантически используется для агрегаций.
- Важно понимать, что DISTINCT работает по всему списку выбранных полей и проектировать запрос соответствующе.
Вопрос 33. Чем отличаются WHERE и HAVING в SQL?
Таймкод: 00:41:32
Ответ собеседника: правильный. Указывает, что WHERE фильтрует строки до группировки, а HAVING — условия к уже сгруппированным данным.
Правильный ответ:
Разница между WHERE и HAVING основана на порядке выполнения запроса и уровне, на котором выполняется фильтрация.
Ключевая идея:
- WHERE фильтрует исходные строки таблицы до агрегирования и группировки.
- HAVING фильтрует уже сгруппированные результаты (агрегаты).
Последовательность (упрощенно) выполнения типичного запроса:
- FROM / JOIN — формирование набора данных
- WHERE — фильтрация строк
- GROUP BY — группировка
- HAVING — фильтрация групп
- SELECT — вычисление выражений и вывод
- ORDER BY / LIMIT
Отсюда следуют правила использования.
- WHERE — фильтрация строк до GROUP BY
Особенности:
- Условия в WHERE не могут использовать агрегатные функции (COUNT, SUM, AVG и т.д.) напрямую.
- WHERE ограничивает набор данных, который пойдет в GROUP BY и агрегаты.
Пример:
Найти количество заказов по пользователям только для статуса 'paid':
SELECT user_id, COUNT(*) AS orders_count
FROM orders
WHERE status = 'paid'
GROUP BY user_id;
Здесь:
- WHERE отбирает только строки с status = 'paid';
- GROUP BY и COUNT работают уже по отфильтрованному набору.
- HAVING — фильтрация после GROUP BY
Особенности:
- Применяется к результатам группировки.
- Может обращаться к агрегатным функциям, т.к. они уже посчитаны.
Пример:
Найти только тех пользователей, у которых больше 3 оплаченных заказов:
SELECT user_id, COUNT(*) AS orders_count
FROM orders
WHERE status = 'paid'
GROUP BY user_id
HAVING COUNT(*) > 3;
Здесь:
- WHERE ограничивает строки по статусу 'paid';
- GROUP BY группирует по user_id;
- HAVING фильтрует группы, оставляя только тех, у кого COUNT(*) > 3.
Еще пример (когда без WHERE):
Все пользователи и отфильтровать по суммарному объему заказов:
SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 1000;
- Типичная ошибка и как её избежать
Ошибка:
- Пытаться использовать агрегаты в WHERE:
-- Ошибка:
SELECT user_id, COUNT(*)
FROM orders
WHERE COUNT(*) > 3
GROUP BY user_id;
Так нельзя, потому что COUNT(*) ещё не посчитан на этапе WHERE.
Правильно:
- Для условий по "обычным" полям (status, date, type) — WHERE.
- Для условий по агрегатам (COUNT, SUM, AVG и т.п.) — HAVING.
- Практический паттерн (Go + SQL)
Пример: хотим в Go получить пользователей с >= N заказами:
SELECT user_id
FROM orders
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY user_id
HAVING COUNT(*) >= $1;
Здесь:
- WHERE задает временной диапазон;
- HAVING задает порог по агрегату.
Такой запрос:
- эффективно использует индексы на created_at и user_id;
- логически прозрачен: сначала сузили данные, потом отфильтровали группы.
Кратко:
-
WHERE:
- фильтрует отдельные строки до группировки;
- нельзя использовать агрегатные функции напрямую.
-
HAVING:
- фильтрует результаты GROUP BY;
- используется для условий на агрегаты и иногда для сложной логики фильтрации групп.
Запоминание:
- "Сначала WHERE по строкам, потом HAVING по группам."
Вопрос 34. Какие варианты сортировки поддерживает ORDER BY в SQL?
Таймкод: 00:41:58
Ответ собеседника: правильный. Называет ASC и DESC, указывает, что по умолчанию сортировка по возрастанию (ASC), что соответствует стандарту.
Правильный ответ:
Ключевые режимы сортировки в ORDER BY:
- ASC (Ascending) — по возрастанию.
- DESC (Descending) — по убыванию.
Если явно не указать направление, используется ASC по умолчанию.
Базовые примеры:
-- По возрастанию (явно)
SELECT id, name
FROM users
ORDER BY name ASC;
-- По возрастанию (неявно, то же самое)
SELECT id, name
FROM users
ORDER BY name;
-- По убыванию
SELECT id, name
FROM users
ORDER BY name DESC;
Многоколонная сортировка:
- Можно сортировать по нескольким полям с разным направлением:
SELECT id, name, created_at
FROM users
ORDER BY name ASC, created_at DESC;
Здесь:
- сначала сортировка по name по возрастанию,
- для одинаковых name — по created_at по убыванию.
Поведение с NULL:
- Зависит от СУБД:
- в PostgreSQL:
- по умолчанию:
- ASC → NULLS LAST,
- DESC → NULLS FIRST.
- можно явно задать:
ORDER BY field ASC NULLS FIRSTORDER BY field DESC NULLS LAST
- по умолчанию:
- в PostgreSQL:
- Это важно для предсказуемости выдачи при сортировке по полям, где возможны NULL.
Пример с NULLS:
SELECT id, last_login_at
FROM users
ORDER BY last_login_at DESC NULLS LAST;
Практические рекомендации:
- Явно указывать ASC/DESC для читаемости и предсказуемости.
- При работе с данными, где возможны NULL, использовать
NULLS FIRST/LAST(если СУБД поддерживает), чтобы избежать неожиданного порядка. - В API/Go-сервисах:
- не доверять "произвольной" сортировке;
- явно задавать поля и направления, чтобы не ломать клиентов при изменениях в БД.
Пример в контексте Go:
rows, err := db.QueryContext(ctx, `
SELECT id, email, created_at
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`, limit, offset)
Итого:
- ORDER BY поддерживает сортировку по возрастанию (ASC, по умолчанию) и по убыванию (DESC), с возможностью комбинировать несколько полей и явно управлять порядком NULL.
Вопрос 35. Для чего используется оператор LIKE и какие шаблоны можно задавать?
Таймкод: 00:42:22
Ответ собеседника: правильный. Описывает LIKE как оператор шаблонного поиска: % для любого количества символов (включая пустую последовательность), _ для ровно одного символа. Приводит корректный пример поиска по фамилии.
Правильный ответ:
Оператор LIKE используется для текстового поиска по шаблону в строковых полях. Он позволяет находить строки, которые частично совпадают с заданным образцом, без использования полнотекстового поиска.
Базовые маски:
%— произвольная последовательность символов (включая пустую)._— ровно один любой символ.
Примеры использования:
- Поиск по подстроке (содержит):
SELECT *
FROM users
WHERE last_name LIKE '%Иванов%';
Найдет:
- "Иванов"
- "Иванова"
- "Петров-Иванов" и т.п.
- Поиск по началу строки:
SELECT *
FROM users
WHERE last_name LIKE 'Иван%';
Найдет:
- "Иванов"
- "Иваненко"
- "Иванов-Сидоров" и т.п.
- Поиск по окончанию строки:
SELECT *
FROM users
WHERE last_name LIKE '%ов';
Найдет:
- "Иванов"
- "Петров"
- "Сидоров" и т.п.
- Использование
_для одного символа:
SELECT *
FROM users
WHERE code LIKE 'AB_3';
Найдет:
- "ABA3", "ABB3", "ABX3" но не "AB33" или "AB123".
- Экранирование специальных символов
Если нужно искать символы % или _ буквально, используют ESCAPE:
SELECT *
FROM logs
WHERE message LIKE '%100\%%' ESCAPE '\';
Ищем строки, содержащие "100%".
- Особенности и практические моменты:
- Регистр:
- В PostgreSQL
LIKEобычно регистрозависим, для нечувствительного поиска используютILIKE. - В MySQL чувствительность зависит от collation.
- В PostgreSQL
- Производительность:
- Условие вида
LIKE '%подстрока%'плохо индексируется и может сканировать всю таблицу. LIKE 'prefix%'лучше, часто может использовать индекс по этому столбцу.
- Условие вида
- Для сложных сценариев поиска:
- используют полнотекстовый поиск (PostgreSQL tsvector/tsquery, ElasticSearch, и др.);
- LIKE — инструмент для базовых/прямолинейных паттернов.
Пример в прикладном контексте (Go):
// Поиск пользователей по email, начинающемуся с prefix
func (r *UserRepo) FindByEmailPrefix(ctx context.Context, prefix string) ([]User, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT id, email
FROM users
WHERE email LIKE $1 || '%'
ORDER BY email
LIMIT 50
`, prefix)
if err != nil {
return nil, err
}
defer rows.Close()
var res []User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Email); err != nil {
return nil, err
}
res = append(res, u)
}
return res, rows.Err()
}
Кратко:
- LIKE используется для поиска строк по шаблону.
%— любое количество символов._— ровно один символ.LIKE 'строка%'обычно эффективно,LIKE '%строка%'— дорого, использовать осознанно.
Вопрос 36. Что такое временные таблицы в SQL и каково их назначение?
Таймкод: 00:43:19
Ответ собеседника: правильный. Описывает временные таблицы как создаваемые на время сессии или транзакции. Отмечает, что сама не использовала, но базовую концепцию передала верно.
Правильный ответ:
Временные таблицы — это таблицы, которые существуют ограниченное время (в рамках сессии или транзакции) и используются для промежуточных расчетов, сложных выборок, оптимизации производительности и упрощения сложной логики запросов.
Ключевые свойства:
- Видимы только в рамках текущей сессии или транзакции (в зависимости от типа).
- Не конфликтуют по данным между разными сессиями.
- Используются для:
- хранения промежуточных результатов;
- декомпозиции сложных запросов;
- подготовки данных для отчетов и сложной агрегации;
- оптимизации, когда повторно используются результаты тяжелых вычислений или джойнов.
Важно: реализация зависит от СУБД (PostgreSQL, MySQL, SQL Server, Oracle и т.д.), но общие идеи похожи.
Базовые сценарии использования:
- Промежуточные результаты сложных запросов
Когда запрос становится слишком громоздким (несколько джойнов, подзапросы, фильтрация), временная таблица:
- упрощает читаемость;
- позволяет повторно использовать результаты;
- иногда улучшает план выполнения.
Пример (SQL-агрегация по шагам):
CREATE TEMP TABLE recent_orders AS
SELECT *
FROM orders
WHERE created_at >= NOW() - INTERVAL '30 days';
CREATE INDEX ON recent_orders (user_id);
SELECT user_id, COUNT(*) AS orders_count
FROM recent_orders
GROUP BY user_id
HAVING COUNT(*) > 3;
Здесь:
- сначала отбираем заказы за 30 дней во временную таблицу;
- индексим временную таблицу под наши запросы;
- далее считаем агрегаты уже по уменьшенному набору данных.
- Повторное использование тяжелых выборок
Если один и тот же сложный набор данных нужен в нескольких частях процедуры/скрипта:
- вместо дублирования подзапросов:
- один раз формируем временную таблицу,
- затем обращаемся к ней.
Это может:
- уменьшить нагрузку на БД;
- сделать планы более предсказуемыми.
- Разбор и нормализация входных данных
Когда:
- загружаем данные "как есть" (bulk load, CSV, файловый импорт);
- нужно:
- почистить;
- проверить;
- сопоставить с основными справочниками;
- только потом залить в боевые таблицы.
Сценарий:
- временная таблица как staging-area:
- валидируем данные;
- пишем ошибки/отчеты;
- корректные данные переносим в основные таблицы.
- Изоляция данных в рамках сессии или транзакции
- Данные во временных таблицах:
- не видны другим сессиям;
- удобно для:
- персонализированных расчетов;
- сложных отчётов на лету;
- хранимых процедур.
Реализация в разных СУБД (кратко):
PostgreSQL:
-
CREATE TEMP TABLEилиCREATE TEMPORARY TABLE:- по умолчанию живет в рамках сессии;
- можно указать
ON COMMIT DELETE ROWSилиON COMMIT DROPдля управления жизненным циклом.
-
Пример:
CREATE TEMP TABLE top_users AS
SELECT user_id, COUNT(*) AS orders_count
FROM orders
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY user_id
HAVING COUNT(*) > 10;
SELECT *
FROM top_users
ORDER BY orders_count DESC;
MySQL:
CREATE TEMPORARY TABLE:- видима только в текущем соединении;
- автоматически дропается при закрытии соединения.
SQL Server:
- Локальные временные таблицы:
#temp_table(только для текущей сессии). - Глобальные временные таблицы:
##temp_table(для всех, пока есть сессии, использующие её).
Практика и инженерные замечания:
- Временные таблицы полезны:
- в сложных отчетах;
- в хранимых процедурах;
- при миграциях и бэченых джобах;
- при оптимизации многократно используемых тяжелых подзапросов.
- Но:
- чрезмерное их использование без анализа планов запросов может ухудшать производительность;
- иногда CTE (WITH) или корректные индексы/нормальная форма запроса дают лучший результат.
Связь с backend (например, Go):
-
В прикладном коде часто вместо временных таблиц:
- используют подзапросы или CTE;
- или в сложных случаях:
- создают временные таблицы в рамках транзакции для batch-операций.
-
Пример (упрощенно):
tx, err := db.BeginTx(ctx, nil)
if err != nil { /* handle */ }
_, err = tx.ExecContext(ctx, `
CREATE TEMP TABLE tmp_ids (id BIGINT PRIMARY KEY);
`)
if err != nil { /* handle */ }
// Вставляем набор id во временную таблицу (batch insert)
// Затем используем tmp_ids в JOIN-ах для обновления/выборки
if err := tx.Commit(); err != nil { /* handle */ }
Итог:
- Временные таблицы:
- живут ограниченно (сессия/транзакция),
- используются для промежуточных и служебных данных,
- помогают упростить сложные запросы и оптимизировать тяжелые операции,
- изолированы от других сессий и не "засоряют" основную схему.
Вопрос 37. В чем разница между типами данных CHAR и VARCHAR в SQL?
Таймкод: 00:44:52
Ответ собеседника: правильный. Объясняет, что CHAR(N) — фиксированная длина, VARCHAR(N) — переменная длина до N символов. Изначально ожидает ошибку при более короткой строке в CHAR, но после уточнения принимает, что недостающие позиции заполняются пробелами.
Правильный ответ:
CHAR и VARCHAR оба используются для хранения строк, но различаются способом хранения, поведением и типичными сценариями применения. Понимание этих отличий важно для корректного дизайна схемы и производительности.
Основные различия:
- Длина и хранение
-
CHAR(N):
- Фиксированная длина.
- Для каждого значения в столбце отводится ровно N символов.
- Если строка короче N:
- она дополняется пробелами до длины N (логически или физически — зависит от СУБД).
- Пример:
- CHAR(5):
- 'AB' хранится как 'AB '.
- CHAR(5):
- Подходит для значений предсказуемой длины:
- коды стран (ISO),
- коды валют,
- фиксированные статусы, двухсимвольные коды и т.п.
-
VARCHAR(N):
- Переменная длина.
- Хранит строки длиной от 0 до N символов.
- Место занимает пропорционально фактической длине + служебная информация о длине.
- Пример:
- VARCHAR(255):
- 'AB' хранится как длина=2 + 'AB', без добивки пробелами.
- VARCHAR(255):
- Подходит для:
- имен, email, адресов, описаний и т.п.
- Поведение при сравнении и пробелах
Особенности зависят от СУБД, но в классическом SQL и ряде реализаций:
- Для CHAR:
- завершающие пробелы при сравнении часто игнорируются.
- 'AB' и 'AB ' считаются равными.
- Для VARCHAR:
- завершающие пробелы обычно учитываются (зависит от collation/настроек).
Важно:
- Это может влиять на запросы, уникальные ключи и логику:
- CHAR(5) с 'AB' и 'AB ' может трактоваться как один и тот же ключ.
- Производительность и применение
Исторически:
- CHAR:
- мог быть быстрее при фиксированной длине, т.к. проще адресация и сканирование.
- В современных СУБД:
- разница меньше, но подходы всё еще важны.
Практические рекомендации:
- Использовать CHAR:
- для строго фиксированной длины и строго формализованных значений:
- country_code CHAR(2),
- currency_code CHAR(3),
- статус, если он всегда ровно 1 символ и т.п.
- для строго фиксированной длины и строго формализованных значений:
- Использовать VARCHAR:
- почти во всех остальных случаях:
- имена, логины, email, пути, описания и т.д.
- почти во всех остальных случаях:
- Ограничения длины
- CHAR(N) и VARCHAR(N):
- N — максимальная длина в символах (в некоторых СУБД — в байтах, важно для Unicode).
- При вставке строки длиннее N:
- корректная СУБД:
- либо выдаст ошибку,
- либо обрежет (в зависимости от настроек, ANSI/strict mode).
- Это уже другая история, не связанная с "короткими" строками для CHAR.
- корректная СУБД:
- Пример с выбором типов
Неплохая схема:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
country CHAR(2), -- ISO-код, всегда 2 символа
currency CHAR(3), -- ISO-код валюты
first_name VARCHAR(100),
last_name VARCHAR(100)
);
Здесь:
- CHAR(2)/CHAR(3) оправданы для стандартизированных кодов.
- Остальное — VARCHAR, потому что длина варьируется.
- Контекст для инженеров backend
При работе из приложения (например, Go):
- Типы CHAR и VARCHAR для драйвера обычно мапятся в string.
- Нюансы:
- trailing-пробелы в CHAR полях:
- могут неожиданно "приезжать" в значения, если СУБД не обрезает их на чтении.
- Лучше явно trim-ить или правильно понимать поведение конкретной СУБД.
- trailing-пробелы в CHAR полях:
Пример чтения в Go:
var country string
err := db.QueryRowContext(ctx,
"SELECT country FROM users WHERE id = $1",
userID,
).Scan(&country)
if err != nil { /* handle */ }
// при CHAR(2) в большинстве СУБД country вернется уже без лишних пробелов,
// но если поведение иное — можно явно strings.TrimSpace(country).
Кратко:
-
CHAR(N):
- фиксированная длина,
- логическая/физическая добивка пробелами,
- полезен для фиксированных кодов.
-
VARCHAR(N):
- переменная длина до N,
- хранит только фактические символы,
- используется для большинства текстовых полей.
Выбор должен быть осознанным, исходя из природы данных, а не "по привычке всегда VARCHAR(255)".
