Перейти к основному содержимому

РЕАЛЬНОЕ СОБЕСЕДОВАНИЕ Тестировщик DCloud - Middle 150+ тыс

· 110 мин. чтения

Сегодня мы разберем живое техническое интервью, в котором кандидат по ручному тестированию демонстрирует уверенные теоретические знания и практический опыт работы с веб-сервисами, 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

Ответ собеседника: правильный. Есть тест-план, но его используют нечасто. В нем описаны виды тестирования, зоны ответственности, инструменты, критерии начала и завершения тестирования, метрики, версия документа. Тест-стратегию формирует лид и согласует ее с продакт-менеджером и владельцем продукта.

Правильный ответ:

Формальный тест-план и тест-стратегия являются частью общего инженерного процесса и должны быть не формальностью для аудита, а рабочим инструментом управления качеством.

Важно разделять понятия:

  • Тест-стратегия — высокоуровневый документ: принципы, подходы, риски, уровни и типы тестирования, общие правила для продукта или домена.
  • Тест-план — конкретизация под релиз, фичу или проект: что именно тестируем, в каких границах, какими силами, какими инструментами и в какие сроки.

Ключевые элементы рабочего тест-плана:

  1. Область и цели:

    • Что покрываем тестированием: конкретные сервисы, модули, интеграции.
    • Явно зафиксированная зона ответственности: что входит и что сознательно не тестируется (out of scope), чтобы избежать ложных ожиданий.
  2. Риски и приоритеты:

    • Явная фиксация критичных областей:
      • платёжные операции;
      • авторизация/аутентификация;
      • интеграции с внешними системами;
      • операции с данными (целостность, миграции).
    • На основе рисков строятся приоритеты тестов и глубина покрытия.
  3. Типы тестирования:

    • Unit, integration, contract testing для микросервисов.
    • API-тестирование (REST/gRPC), UI, e2e.
    • Нагрузочное, стресс-тестирование для ключевых сервисов.
    • Безопасность (basic security checks, auth flows).
    • Регрессия: что автоматизировано, что остается в ручном виде.
  4. Ответственность и взаимодействие:

    • Кто отвечает за:
      • написание автотестов (backend, QA automation);
      • ручное тестирование критических сценариев;
      • поддержку тестовых данных и сред;
      • принятие решения о готовности к релизу.
    • Важный момент: тест-план — не только зона QA, это общий артефакт команды разработки.
      • Например, backend-разработчики отвечают за unit и integration тесты своих сервисов.
      • QA — за e2e сценарии, exploratory testing и проверку рисков.
  5. Инструменты:

    • Для API: Postman, Newman, Swagger/OpenAPI, автотесты на Go.
    • Для UI: автотесты (Playwright/Cypress/Selenium), ручные чек-листы.
    • Для backend-сервисов:
      • Тестовые окружения в Kubernetes/Docker.
      • Использование отдельных тестовых БД (PostgreSQL), seed-данных и миграций.
    • Трекеры: Jira/YouTrack для задач и дефектов, Confluence/Notion для документации, Zephyr/TestRail для тест-кейсов.
  6. Критерии начала и окончания тестирования:

    • Start:
      • Собрана и задеплоена тестовая версия.
      • Есть спецификация/acceptance-критерии.
      • Описание API и контрактов актуально (OpenAPI/gRPC proto).
    • Stop:
      • Критические и блокирующие дефекты устранены.
      • Пройдены обязательные чек-листы и автотесты.
      • Достигнуты целевые метрики качества (например, 0 blocker, 0 critical, покрытие ключевых сценариев).
  7. Метрики:

    • Количество дефектов по 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 чаще всего применяется как центральный компонент событийной и микросервисной архитектуры для:

  • асинхронного взаимодействия сервисов;
  • интеграции между доменами;
  • обработки потоков данных в реальном времени;
  • построения отказоустойчивых и масштабируемых пайплайнов.

Основные сценарии использования:

  1. Асинхронное взаимодействие микросервисов:

    • Вместо прямых HTTP/gRPC вызовов:
      • Продюсер публикует событие (например, UserCreated, OrderPaid) в топик.
      • Один или несколько консюмеров подписаны на этот топик и независимо обрабатывают событие.
    • Преимущества:
      • Ослабленная связанность между сервисами.
      • Устойчивость к временной недоступности получателя.
      • Возможность легко добавлять новых потребителей без изменения продюсера.
  2. Event-driven архитектура:

    • Kafka используется как "журнал событий" (event log).
    • Сервисы не только реагируют на события, но и строят свои проекции данных:
      • Например, сервис аналитики подписывается на события заказов и формирует агрегаты.
    • Важный момент: события должны быть:
      • неизменяемыми;
      • семантически стабильными (backward compatible);
      • описывать факт ("что произошло"), а не команду ("сделай").
  3. Интеграция и data pipelines:

    • Потоковая загрузка данных в DWH/аналитику.
    • Репликация событий из транзакционных систем.
    • Связка с Kafka Connect и Kafka Streams/Flink для трансформации и обогащения данных.
  4. Масштабирование и отказоустойчивость:

    • Топики разбиваются на партиции, что позволяет:
      • масштабировать потребление по нескольким инстансам сервиса;
      • обрабатывать большие объемы данных параллельно.
    • Репликация партиций между брокерами повышает надежность.

Ключевые концепции, которые важно понимать:

  • Топик: логический канал сообщений.
  • Партиция: упорядоченный лог внутри топика; порядок сообщений гарантирован только внутри партиции.
  • Ключ сообщения:
    • Определяет, в какую партицию попадет сообщение.
    • Используется для сохранения порядка по ключу (например, все события по одному пользователю).
  • 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" — дополнение контроля качества метриками, логами, нагрузочными и эксплуатационными аспектами уже в проде.

Ключевые этапы и роль тестирования:

  1. Инициация и продуктовая идея:

    • Подключение уже на этапе формирования гипотезы и бизнес-требований.
    • Что делают специалисты по качеству:
      • помогают выявить риски и критичные бизнес-сценарии;
      • уточняют, какие метрики успеха фичи подлежат проверке;
      • задают вопросы о граничных случаях, ограничениях, регуляторных требованиях, безопасности.
    • Результат: бизнес-требования формируются более полными и проверяемыми.
  2. Аналитика и формализация требований:

    • Участие в обсуждении требований, user stories, acceptance criteria.
    • Основные задачи:
      • проверка требований на:
        • полноту (нет ли отсутствующих сценариев);
        • непротиворечивость;
        • тестопригодность (можно ли однозначно проверить?);
        • конкретику (избегание размытых формулировок).
      • Формирование наборов будущих тестовых сценариев уже на этапе требований.
    • Если здесь подключаться системно — сильно снижается количество дефектов реализации.
  3. Архитектура и дизайн решения:

    • В идеальной команде специалисты по качеству участвуют в обсуждении архитектурных решений:
      • интеграции с другими системами;
      • использование очередей, брокеров, кешей, внешних API;
      • механизмы логирования, трассировки, фича-флагов, graceful degradation.
    • Почему это важно:
      • многие проблемы с надежностью, наблюдаемостью и тестируемостью закладываются на этом этапе.
    • Например:
      • требование: "операция должна быть идемпотентной";
      • требование: "должны быть метрики ошибок и latencies для каждого ключевого эндпоинта";
      • договоренность: "каждый публичный API имеет контракт (OpenAPI/proto) и тестируемую схему".
  4. Этап разработки:

    • Тестирование не ждет "готовый продукт":
      • разработчики покрывают код 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-тесты с моками репозитория;
    • тестируемость учитывается уже при проектировании.
  5. Этап интеграционного и системного тестирования:

    • Подключение к:
      • интеграции микросервисов;
      • проверке очередей (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);
    • И в тестах проверяется:
      • доступность старых колонок;
      • корректность новых индексов под частые запросы.
  6. Pre-production / UAT:

    • Проверка на окружении, максимально близком к боевому.
    • Валидация:
      • ключевых бизнес-сценариев;
      • ролей и прав доступа;
      • производительности под реальными нагрузками;
      • поведения в случае отказов внешних сервисов.
    • Решение go/no-go принимается на основе:
      • статуса тестов;
      • дефектов по severity;
      • метрик качества.
  7. 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.

Тест:

  1. Через API:
POST /api/v1/orders
Body: { "user_id": 1, "amount": 100 }
→ ожидаем 201 Created
  1. Проверяем в тестовом окружении БД:
SELECT status, amount
FROM orders
WHERE user_id = 1
ORDER BY created_at DESC
LIMIT 1;

Ожидаем: статус "created", amount = 100.

  1. При необходимости — проверяем, что в тестовом 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 видит успешный результат.
  • Оформление заказа:
    • авторизация → выбор товара → создание заказа → оплата → изменение статуса → запись в БД → событие в очередь → обновление дашборда или уведомление.

Практический пример сквозного сценария (микросервисная архитектура):

Сценарий: "Создать заказ и убедиться, что он отразился в системе аналитики".

Последовательность:

  1. UI/клиент отправляет запрос:
POST /api/v1/orders
Authorization: Bearer <token>
Body: { "user_id": 123, "item_id": 456, "amount": 2 }
  1. API-gateway проксирует запрос в сервис заказов.

  2. Сервис заказов:

    • валидирует данные;
    • проверяет наличие товара (через сервис каталога);
    • создает запись в PostgreSQL:
    INSERT INTO orders (user_id, item_id, amount, status)
    VALUES (123, 456, 2, 'created');
    • публикует событие в Kafka-топик order-created.
  3. Сервис аналитики:

    • читает события order-created из Kafka;
    • обновляет агрегаты или витрины в своей БД.
  4. 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 тест, по сути, тоже оформляется как тест-кейс, но:

  1. Уровень охвата:
  • Обычный тест-кейс:

    • Часто нацелен на:
      • одно требование,
      • один модуль,
      • один API-метод,
      • одну бизнес-правку.
    • Может быть:
      • unit-уровня (черный/белый ящик),
      • интеграционного уровня,
      • частичного функционального сценария.
    • Пример:
      • "Поля формы регистрации валидируются корректно при неверном email".
  • End-to-end тест:

    • Проверяет полный сквозной пользовательский или бизнес-процесс:
      • от входной точки (UI/API/внешний вызов)
      • до всех ключевых внутренних и внешних взаимодействий (микросервисы, БД, очереди, внешние API)
      • до финального наблюдаемого результата.
    • Охватывает много требований сразу:
      • бизнес-правила;
      • интеграции;
      • права доступа;
      • корректность данных по всей цепочке.
  1. Цель:
  • Обычный тест-кейс:

    • Подтвердить корректность конкретного функционала или правила.
    • Локализовать дефекты в пределах одного компонента или функции.
  • End-to-end тест:

    • Подтвердить, что вся система в реальной конфигурации:
      • выполняет целевой сценарий без разрывов;
      • корректно интегрирована;
      • не ломает критичные бизнес-потоки.
    • Это "проверка боевой готовности" ключевых сценариев.
  1. Контекст микросервисов и Go/Backend:

Пример обычного тест-кейса (интеграционный, описывает один аспект):

  • Проверить, что метод /api/v1/users:
    • при передаче корректных данных создает пользователя;
    • возвращает 201 и JSON с id.

Пример end-to-end теста:

Сценарий: "Регистрация пользователя и активация через email".

  • Шаги:
      1. Вызвать публичный API регистрации:
      POST /api/v1/users
      Body: { "email": "user@example.com", "password": "StrongP@ss1" }
      → Ожидаем 201.
      1. Убедиться, что:
      • запись появилась в users в PostgreSQL со статусом "pending":

        SELECT status FROM users WHERE email = 'user@example.com';
        -- Ожидаем: pending
      1. Проверить, что сервис нотификаций отправил письмо:
      • через mock SMTP или тестовый инбокс.
      1. Перейти по ссылке активации (эмуляция клика по токену).
      1. Проверить, что статус пользователя стал "active" в БД.
      1. Проверить, что пользователь может авторизоваться и получить токен.

Этот сценарий:

  • проходит через несколько сервисов (auth, users, mail);
  • использует БД, очереди/события (если есть), внешнюю инфраструктуру;
  • объединяет несколько бизнес-требований: регистрация, активация, безопасность, корректность статусов.
  1. Практический вывод:
  • 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 внешняя система.

Классические методы интеграционного тестирования:

  1. Подход "Большой взрыв" (Big Bang):

Суть:

  • Все (или почти все) модули/сервисы интегрируются сразу и тестируются как единое целое.

Плюсы:

  • Быстрый старт, если уже есть собранное окружение.

Минусы:

  • Очень сложно локализовать дефекты: непонятно, на стыке каких компонент сломалось.
  • Высокая стоимость поддержки, нестабильность.
  • В микросервисной архитектуре превращается в хрупкие e2e, если нет системного подхода.

Использовать умеренно:

  • Для финальной проверки, но не как основной метод.
  1. Снизу вверх (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)
}

Этот тест уже интеграционный: он проверяет связку код + реальная БД + миграции.

  1. Сверху вниз (Top-Down):

Суть:

  • Начинают с верхнего уровня:
    • API, сервисы, сценарии.
  • Нижележащие компоненты, которые еще не готовы (БД, внешние сервисы), подменяются заглушками (stubs/mocks/fakes).
  • Затем заглушки постепенно заменяются реальными реализациями.

Плюсы:

  • Ранний фокус на бизнес-сценариях и API.
  • Можно валидировать дизайн контрактов до реализации всех зависимостей.

Минусы:

  • Риск, что заглушки будут "слишком оптимистичными" и не отразят реальное поведение.
  • Нужна дисциплина, чтобы потом заменить их реальными компонентами и не жить в "фейковом мире".
  1. Комбинированный (Сэндвич / Hybrid):

Суть:

  • Сочетание подходов сверху-вниз и снизу-вверх.
  • Пример:
    • уже протестировали слой работы с БД (bottom-up);
    • параллельно тестируем верхний уровень API с заглушками для внешних сервисов (top-down);
    • затем стыкуем всё вместе.

Плюсы:

  • Баланс: фундамент проверен, сценарии видны.
  • Параллельная разработка и тестирование.

Минусы:

  • Сложнее в организации, требует продуманной стратегии.

Реальные практики интеграционного тестирования в современных проектах:

Помимо классических подходов, в инженерной практике широко используются:

  • Контрактное тестирование (Consumer-Driven Contracts):

    • Особенно важно в микросервисах.
    • Идея: сервис-потребитель формализует свои ожидания к API/событиям сервиса-поставщика.
    • Эти контракты автоматически проверяются против реализации.
    • Снижает количество дефектов на интеграции и делает E2E легче.
  • Тесты с реальными зависимостями в изолированном окружении:

    • Использование Docker / Testcontainers:
      • реальная PostgreSQL;
      • локальная Kafka/Redis;
      • запуск Go-сервисов рядом.
    • Это даёт "настоящую" интеграцию без зависимости от общего стенда.

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);
  • после крупных изменений и рефакторингов;
  • когда требования сырые, неполные или часто меняются.

Основные техники и форматы исследовательского тестирования:

  1. Сессионное исследовательское тестирование (Session-based testing)
  • Работа делится на четко ограниченные по времени сессии (обычно 60–120 минут).
  • У каждой сессии есть:
    • charter (цель): что мы исследуем;
    • scope: область (модуль, сервис, сценарий);
    • ограничения: что не трогаем;
    • отчет: что проверили, что нашли, какие риски увидели.
  • Пример charter:
    • "Исследовать создание и редактирование заказов через API: граничные значения, некорректные данные, поведение при недоступности внешнего сервиса оплат".

Это добавляет управляемость и прозрачность, в отличие от "хаотичного потыкать интерфейс".

  1. Туристическое тестирование (Tours)

Используются "туры" — метафорические маршруты, чтобы системно покрывать продукт под разными углами:

  • Data Tour:
    • исследование поведения с разными наборами данных:
      • минимальные/максимальные значения;
      • пустые коллекции;
      • массовые данные;
      • "грязные" данные.
  • Interrupted Tour:
    • проверки при обрыве соединения, таймаутах, ошибках внешних сервисов.
  • Configuration Tour:
    • разные роли, настройки, фича-флаги.
  • Money Tour:
    • всё, что связано с деньгами, биллингом, ключевыми метриками бизнеса.

Для backend-систем и Go-сервисов туры хорошо ложатся на:

  • сценарии с отказами (circuit breaker, ретраи);
  • неконсистентные данные в БД;
  • задержки/ошибки Kafka, Redis, внешних API.
  1. Чартеры (test charters)

Чартер — это мини-спецификация для одной исследовательской сессии:

  • Что исследуем.
  • Какой риск/гипотезу хотим проверить.
  • Какие ограничения.
  • Что считаем успехом.

Примеры чартеров:

  • "Проверить API авторизации на обработку некорректных токенов, истекших токенов и токенов несуществующих пользователей."
  • "Исследовать поведение сервиса заказов при массовом создании заказов и временной недоступности сервиса оплаты."
  1. Использование техник тест-дизайна внутри исследовательского тестирования

Исследовательское тестирование — не хаос. В нем осознанно применяются классические техники:

  • Эквивалентное разбиение и граничные значения:
    • для входных данных, параметров API, фильтров, пагинации.
  • Попарное / комбинаторное тестирование:
    • чтобы эффективно покрывать комбинации параметров.
  • Ошибочные предположения (Error Guessing):
    • осознанная проверка типичных "болей":
      • null/empty;
      • дубли;
      • конкурирующие запросы;
      • нарушение предположений о порядке.
  1. Инструменты и практики в контексте 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 OK
  • POST /api/v1/users → 201
  • GET /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 → проверить отображение и валидацию;
      • не идем по всему регрессу профиля, платежей, заказов и т.д.

Как это применять на практике (зрелый процесс):

Разумная последовательность для фичи/релиза может выглядеть так:

  1. На test/stage окружении:

    • Смоук-тест:
      • убедиться, что сервисы поднялись, базовые функции работают.
    • Санити-тест:
      • проверить новую фичу или фиксы по основным сценариям;
      • убедиться, что изменения не ломают себя же.
    • Если санити провален — нет смысла запускать полный регресс.
  2. Если санити успешен:

    • Регрессионное тестирование:
      • прогон автотестов;
      • при необходимости целевые ручные проверки по зонам риска.
  3. Перед и после выката на prod:

    • Pre-prod:
      • смоук + ключевые регрессионные сценарии.
    • Prod:
      • post-deploy смоук:
        • health-check,
        • критичные бизнес-операции на тестовых или безопасных сценариях.

Кратко:

  • Смоук:

    • "Жив ли билд? Имеет ли смысл тестировать дальше?"
    • Быстро, поверхностно, по ключевым точкам.
  • Санити:

    • "То, что только что поменяли, вообще работает по-основному?"
    • Узко, целево, вокруг изменений/фиксов.
  • Регрессия:

    • "Не сломали ли мы что-нибудь из того, что раньше работало?"
    • Широко, глубоко, на весь важный функционал.

Такое различение и правильное применение трех уровней позволяет строить эффективный пайплайн: быстро отсекать бракованные сборки, точечно проверять изменения и гарантировать стабильность продукта перед релизом.

Вопрос 13. Какие виды тестирования относятся к нефункциональным?

Таймкод: 00:19:35

Ответ собеседника: правильный. Перечисляет тестирование восстановления и отказоустойчивости, производительности (нагрузочное, стресс, объёмное, конкурентное, масштабируемость, стабильность), безопасности, удобства использования, интерфейса, установки, локализации, интернационализации, доступности и кроссбраузерности.

Правильный ответ:

Нефункциональное тестирование оценивает не то, "что" система делает (функциональность), а "как" она это делает в реальных условиях эксплуатации. Это про качество сервиса: скорость, надежность, устойчивость, безопасность, удобство, совместимость, эксплуатационные характеристики. В контексте современных web-систем, микросервисов и backend-сервисов на Go это критично.

Ниже — ключевые виды нефункционального тестирования с практическими акцентами.

Производительность (Performance Testing)

Включает несколько подтипов:

  • Нагрузочное тестирование (Load Testing):

    • Проверяет, как система работает под ожидаемой (нормальной) и слегка повышенной нагрузкой.
    • Цель: убедиться, что при целевых RPS/пользователях сохраняются:
      • адекватное время ответа;
      • отсутствие деградации и ошибок.
    • Пример:
      • 500 RPS к /api/v1/orders, p95 < 300 мс, error rate < 1%.
  • Стресс-тестирование (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

Ответ собеседника: правильный. Перечисляет: уникальный идентификатор, краткое и полное описание, шаги воспроизведения, ожидаемый и фактический результат, ссылку на требования, при необходимости — информацию об обходе, воспроизводимость, важность, срочность, комментарии, вложения и окружение. Уточняет, что часть полей используется выборочно.

Правильный ответ:

Хороший баг-репорт — это не бюрократический документ, а инженерный артефакт, который позволяет разработчику:

  • быстро понять суть проблемы;
  • однозначно воспроизвести дефект;
  • оценить влияние на систему;
  • эффективно исправить, не тратя время на уточнения.

Минимальный обязательный набор атрибутов для практики промышленной разработки должен обеспечивать:

  • однозначность;
  • воспроизводимость;
  • привязку к требованиям;
  • понятность при чтении человеком, не присутствовавшим при обнаружении.

Ключевые обязательные атрибуты:

  1. Уникальный идентификатор
  • Генерируется системой трекинга (Jira, YouTrack и т.п.).
  • Нужен для ссылок в:
    • коммитах,
    • MR/PR,
    • отчётах,
    • релиз-нотах.

Пример: PAY-1243, API-567.

  1. Краткое описание (Summary / Title)
  • Сжато и конкретно отражает проблему.
  • Формат:
    • "Где? Что не так? При каких условиях (если влезает)?"
  • Хороший пример:
    • "API /orders/{id}: 500 при запросе несуществующего id"
    • "UI: дубль платежа при двойном клике по кнопке 'Оплатить'"
  • Плохой пример:
    • "Не работает", "Ошибка", "Баг".
  1. Окружение (Environment)

Обязательно, особенно для распределенных систем:

  • стенд: dev, test, stage, prod;
  • версия приложения/сервиса (build number, git commit, tag);
  • важные параметры:
    • браузер и версия;
    • ОС;
    • моб. устройство;
    • конфигурация (feature flags, регион, тип клиента);
    • версия API (v1/v2).

Для backend/Go-сервисов:

  • указать:
    • сервис/микросервис;
    • версию docker-образа;
    • важные фича-флаги.
  1. Шаги воспроизведения (Steps to Reproduce)
  • Четкая, по возможности нумерованная последовательность действий.
  • Не "сделать как обычно", а конкретно:
    • входные данные;
    • вызовы API;
    • навигация по UI;
    • предварительные условия (preconditions).

Примеры:

  • Для UI:

      1. Зайти под пользователем user@test.com
      1. Перейти в "Мои заказы"
      1. Нажать "Создать заказ" с параметрами ...
      1. Нажать "Сохранить"
  • Для API:

    • указать точный запрос:

      POST /api/v1/orders
      Body: { "user_id": -1, "amount": 100 }
  1. Ожидаемый результат
  • Конкретное описание, основанное:
    • на требованиях,
    • спецификациях,
    • согласованных бизнес-правилах.
  • Не "должно работать", а:
    • "Должен вернуться HTTP 400 с кодом ошибки 'INVALID_USER_ID'".

Это помогает разработчику понять:

  • какое поведение считается корректным;
  • нет ли ошибки в ожиданиях тестировщика.
  1. Фактический результат
  • Реально наблюдаемое поведение.
  • С максимальной конкретикой:
    • текст сообщений;
    • коды ответов;
    • изменения в данных;
    • видимые эффекты в UI.

Пример:

  • "Возвращается 500 Internal Server Error, в логах 'pq: null value in column "user_id"'".
  1. Воспроизводимость (Reproducibility)
  • Насколько стабилен дефект:
    • всегда,
    • иногда (процент),
    • при определенных условиях (нагрузка, конкретные данные).
  • Для нестабильных багов:
    • указать, сколько раз пробовали и сколько раз поймали.

Это влияет на:

  • приоритизацию;
  • подход к диагностике (логи, метрики, профилирование).
  1. Важность и приоритет (Severity / Priority)

Должны быть зафиксированы явно:

  • Severity (насколько серьезны последствия):
    • Blocker, Critical, Major, Minor, Trivial.
  • Priority (как быстро исправлять):
    • P0, P1, P2...

Примеры:

  • Blocker/Critical:
    • падение сервиса,
    • потеря данных,
    • невозможность оформить заказ/оплатить.
  • Minor:
    • некорректный отступ в UI,
    • опечатка в тексте.

Важно:

  • Severity оценивает тестировщик (по воздействию).
  • Priority обычно уточняет владелец продукта / тимлид / менеджер.
  1. Ссылки на требования / спецификации / макеты
  • Желательно всегда указывать, на основании чего считаем поведение некорректным:
    • Jira-история;
    • Confluence-страница;
    • API-спецификация (OpenAPI/Swagger);
    • дизайн в Figma.

Это:

  • уменьшает дискуссии "это баг или фича";
  • помогает синхронизировать ожидания.
  1. Вложения и дополнительная диагностика

Не формально "опционально", а практически критично:

  • Скриншоты, видео (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:
      1. POST /api/v1/orders с body:
      • { "user_id": 999999, "amount": 100 }
  • Expected:
    • HTTP 400 с кодом USER_NOT_FOUND.
  • Actual:
    • HTTP 500, в логах: pq: insert or update on table "orders" violates foreign key constraint "orders_user_id_fkey".
  • Severity:
    • Major (ошибка сервера, неправильная обработка валидации).
  • Links:
    • Jira: PROJ-101, API spec: Confluence link.
  • Attachments:
    • curl-запрос, фрагмент логов, trace-id.

Итог:

Обязательные атрибуты хорошего баг-репорта:

  • идентификатор;
  • ясный заголовок;
  • окружение;
  • четкие шаги воспроизведения;
  • ожидаемый результат;
  • фактический результат;
  • данные о воспроизводимости;
  • severity (и, желательно, priority);
  • ссылка на требования/спецификацию;
  • вложения (логи, скриншоты, запросы) для ускорения анализа.

Такая структура минимизирует "ping-pong" между QA и разработчиками и ускоряет цикл обнаружения–исправления–проверки дефектов.

Вопрос 16. Какой уровень серьёзности и приоритета у дефекта с пропущенной запятой в описании товара на сайте, и как это обосновать?

Таймкод: 00:22:18

Ответ собеседника: правильный. Относит орфографическую ошибку к низкой серьёзности, так как не влияет на функциональность, но допускает высокий приоритет из-за влияния на имидж и восприятие компании пользователями.

Правильный ответ:

Такой дефект обычно классифицируется как:

  • серьёзность (Severity): низкая (Minor/Trivial);
  • приоритет (Priority): зависит от контекста, может быть от низкого до высокого.

Обоснование по уровням:

  1. Серьёзность (Severity):
  • Критерий: насколько дефект влияет на работоспособность и бизнес-логику.
  • Пропущенная запятая:
    • не ломает функционал;
    • не мешает оформлению заказов;
    • не искажает юридически значимые условия (в типичном случае);
    • не ведёт к потере данных или денег.
  • Следовательно:
    • обычно это Minor или Trivial:
      • дефект интерфейса/контента;
      • косметический, не функциональный.

Исключения:

  • Если пунктуация меняет смысл так, что:
    • может ввести в заблуждение о цене, условиях акции, юридических обязательствах,
    • или нарушает юридически важный текст,
    • тогда серьёзность может быть выше (до Major), но это особый случай и должен быть обоснован.
  1. Приоритет (Priority):

Приоритет определяется не техническим ущербом, а:

  • влиянием на:
    • репутацию бренда;
    • восприятие качества;
    • целевую аудиторию;
  • местом и видимостью дефекта:
    • главная страница,
    • ключевые маркетинговые блоки,
    • платные рекламные лендинги,
    • банковские/юридические продукты,
    • крупный B2B-клиент.

Типичные подходы:

  • Низкий/средний приоритет:

    • если ошибка в малозаметном описании, на внутренней странице, не бьет по довериям.
    • Может быть запланирована в один из ближайших спринтов, без экстренности.
  • Средний/высокий приоритет:

    • если ошибка:
      • на главной;
      • в блоке, который видит каждый новый пользователь;
      • в описании премиум-продукта;
      • в презентации для важного партнера.
    • Здесь аргументация:
      • отражает уровень внимательности компании;
      • может снижать доверие к бренду, особенно в чувствительных доменах (банк, медицина, госуслуги).
  1. Как обосновать решение на практике:

Корректный ответ на интервью должен показать умение разделять:

  • Severity — про технический/функциональный ущерб.
  • Priority — про бизнес, имидж, контекст и сроки.

Пример формулировки:

  • Severity: Trivial (UI/content issue, функциональность не затронута).
  • Priority:
    • Если это главный экран/ключевой промо-блок:
      • можно поставить High, с обоснованием:
        • влияет на впечатление всех новых пользователей,
        • выглядит непрофессионально, бьет по бренду.
    • Если это глубоко внутри каталога:
      • 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

Ответ собеседника: правильный. Перечисляет ключевые принципы: невозможность исчерпывающего тестирования, тестирование показывает наличие дефектов, а не их отсутствие, необходимость раннего тестирования, контекст-зависимость, кластеризацию дефектов, эффект пестицида и заблуждение об отсутствии ошибок. Для борьбы с эффектом пестицида предлагает менять тестовые данные и актуализировать тест-кейсы.

Правильный ответ:

Ключевые принципы тестирования (в прикладном, инженерном виде):

  1. Невозможность исчерпывающего тестирования
  • Полностью проверить все комбинации входных данных, состояний, окружений нереально.
  • Следствие:
    • используем риск-ориентированный подход;
    • применяем техники тест-дизайна (классы эквивалентности, граничные значения, попарное тестирование);
    • фокусируемся на:
      • критичных бизнес-сценариях,
      • сложных интеграциях,
      • исторически проблемных местах.
  1. Тестирование показывает наличие дефектов, а не их отсутствие
  • Даже если все тесты зелёные, это не доказательство идеального качества.
  • Следствие:
    • важно качество самих тестов и покрытие;
    • нужны разные уровни: unit, integration, contract, E2E, нефункциональные;
    • анализ инцидентов и логов в проде — часть реальной картины.
  1. Раннее тестирование (Shift Left)
  • Чем раньше найден дефект (на этапе требований, дизайна, код-ревью), тем дешевле его исправление.
  • Следствие:
    • ревью требований и API-спецификаций;
    • статический анализ;
    • unit и контрактные тесты как обязательная часть разработки;
    • тестируемость и наблюдаемость закладываются в архитектуру.
  1. Кластеризация дефектов
  • Дефекты склонны скапливаться в одних и тех же модулях:
    • сложная логика,
    • старый легаси-код,
    • активно меняющиеся компоненты,
    • низкое качество дизайна.
  • Следствие:
    • осознанно усиливаем тестовое покрытие этих зон;
    • применяем более строгие практики: code review, дополнительные интеграционные тесты, метрики качества.
  1. Контекст-ориентированность
  • Подходы, глубина и инструменты тестирования зависят от:
    • домена (финтех, медицина, e-commerce, gov),
    • типа продукта (API, B2C web, B2B-платформа, внутренний сервис),
    • критичности (деньги, безопасность, SLA).
  • Следствие:
    • не существует универсального "чек-листа на всё";
    • тестовая стратегия должна учитывать архитектуру (микросервисы, очереди, БД) и бизнес-приоритеты.
  1. Заблуждение об отсутствии ошибок
  • "Нет найденных дефектов" != "Нет дефектов".
  • Часто означает:
    • слабые тесты,
    • плохой охват,
    • тестирование не там, где нужно.
  • Следствие:
    • регулярно переоцениваем покрытие;
    • проверяем, соответствуют ли тесты актуальным рискам и изменившейся архитектуре;
    • используем метрики (вообще находили ли мы баги в этой области, какие?).
  1. Эффект пестицида

Суть эффекта пестицида:

  • Если долго выполнять один и тот же набор тестов без изменений:
    • со временем они перестают находить новые дефекты;
    • система адаптируется под эти проверки (фиксируем только то, что поймали);
    • "дыры" остаются вне зоны видимости.

Это особенно заметно:

  • в регрессионных наборах автотестов;
  • в шаблонных smoke/regression-checklist-ах;
  • при неизменяемых E2E сценариях в быстро развивающейся системе.

Как правильно с ним бороться (прикладные практики):

  1. Регулярный пересмотр тестов
  • Периодически анализировать:
    • насколько тесты соответствуют текущей архитектуре и бизнес-логике;
    • покрывают ли они новые фичи, интеграции, миграции.
  • Удалять устаревшие тесты, которые больше не несут ценности.
  • Обновлять ожидаемые результаты и сценарии под актуальные требования.
  1. Расширение и варьирование тестовых данных
  • Для API, БД, микросервисов:

    • менять набор входных данных:
      • граничные значения;
      • редкие кейсы;
      • «грязные» данные;
      • большие объемы/массовые операции.
  • Для примера:

    Вместо одного-двух "красивых" запросов:

    POST /api/v1/users
    { "email": "test@example.com", "age": 25 }

    Добавить:

    • разные домены email;
    • очень длинные строки;
    • unicode;
    • параллельные запросы (concurrency);
    • сценарии с частично валидными данными.
  1. Использование разных техник тест-дизайна
  • Не ограничиваться только позитивными/несколькими негативными кейсами.
  • Применять:
    • классы эквивалентности и граничные значения;
    • попарное тестирование для комбинаций;
    • таблицы принятия решений и причинно-следственные связи для сложных правил;
    • диаграммы состояний для жизненных циклов сущностей.
  • Это позволяет выделять новые сценарии, которые старый набор тестов не покрывал.
  1. Инцидент-ориентированное улучшение тестов
  • Каждый найденный в проде дефект:
    • должен приводить не только к фиксу кода,
    • но и к добавлению или корректировке тестов, которые:
      • воспроизводят сценарий;
      • закрывают целый класс подобных проблем.
  • Это эволюционирует регрессию по реальным болям системы.
  1. Исследовательское тестирование (exploratory)
  • Регулярно дополнять формальные автотесты исследовательскими сессиями:
    • новые комбинации;
    • необычные последовательности действий;
    • проверки под нагрузкой, при отказах, с измененными конфигурациями.
  • Особенно важно для микросервисов, асинхронных процессов, сложных интеграций.
  1. Включение архитектуры и логов в мышление о тестах
  • Анализировать:
    • где могут быть точки отказа: очереди, транзакции, ретраи, кеши, гонки.
    • что показывают логи и метрики в проде:
      • частые 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

Ответ собеседника: правильный. Говорит, что полная автоматизация по сути недостижима. Отмечает минусы: высокая стоимость внедрения и поддержки, потребность в квалифицированных специалистах, сложность выбора инструментов и связанных с этим рисков, необходимость постоянной актуализации автотестов при изменении требований. Подчеркивает, что автотесты не заменяют человека и плохо покрывают аспекты вроде вёрстки.

Правильный ответ:

Идея "полностью всё автоматизировать" выглядит привлекательно, но в реальных продуктах приводит к архитектурным, организационным и экономическим проблемам. Важно понимать, почему "полная автоматизация" — не цель, а анти-паттерн.

Ключевые моменты:

  • Автотесты критически нужны.
  • Но они:
    • не покрывают весь спектр рисков,
    • стоят дорого,
    • сами являются кодом с техническим долгом,
    • требуют архитектуры, процессов и людей.

Основные минусы и ограничения "полной автоматизации":

  1. Высокая стоимость разработки и поддержки

Автотесты — это код:

  • их нужно:
    • спроектировать;
    • написать;
    • рефакторить;
    • адаптировать под изменения архитектуры и требований;
    • поддерживать инфраструктуру (CI, стенды, данные, контейнеры).

Риски:

  • при каждом изменении API, UI, схемы БД, флоу:
    • ломается пачка тестов;
    • команда тратит ресурсы не на развитие продукта, а на бесконечный ремонт тестов.

Вывод:

  • Авто-тесты должны быть экономически оправданы:
    • автоматизируем повторяемое и критичное;
    • не гонимся за формальной "100% автоматизацией".
  1. Хрупкость и ложное чувство безопасности

При попытке "автоматизировать всё":

  • Появляется огромное количество UI/E2E тестов:
    • медленных;
    • нестабильных;
    • чувствительных к верстке, таймингам, мелким изменениям.
  • Результат:
    • флаки-тесты;
    • красные пайплайны по несущественным причинам;
    • у команды выгорает доверие к тестам (их просто начинают игнорировать).
  • При этом:
    • наличие тысяч зелёных тестов создаёт иллюзию "у нас всё хорошо",
    • хотя:
      • тесты могут быть поверхностными,
      • не покрывать реальные риски,
      • проверять только "золотые пути".

Вывод:

  • качество определяется не числом автотестов, а тем:
    • какие риски/сценарии они покрывают,
    • как они встроены в архитектуру и процессы.
  1. Ограниченность того, что можно адекватно автоматизировать

Есть области, где машина по определению слабее:

  • UX и визуальное восприятие:
    • автотест может проверить наличие элемента, но не:
      • удобство,
      • логичность,
      • восприятие пользователем.
  • Контент, копирайтинг, тон коммуникации:
    • ошибки смысла, культурные нюансы.
  • Сложные и нестандартные сценарии поведения пользователя:
    • хаотичная навигация;
    • "странные" последовательности действий;
    • комбинирование функций.

Часть визуальных и версточных проверок автоматизировать можно (визуальные снапшоты, сравнение скриншотов), но:

  • это дорого,
  • часто хрупко,
  • не заменяет здравую ручную проверку ключевых экранов.
  1. "Полная автоматизация" плохо масштабируется при высокой изменчивости продукта

В продуктах с:

  • активным A/B тестированием;
  • динамичными UI;
  • быстрым изменением API;
  • частыми фича-флагами;

жёсткие E2E/UI автотесты:

  • становятся постоянным источником боль и задержек;
  • не успевают адаптироваться под изменения.

Лучший подход:

  • сильный слой:
    • unit-тестов (особенно для бизнес-логики в Go);
    • интеграционных тестов (с БД, Kafka, очередями);
    • контрактных тестов между микросервисами;
  • ограниченное количество устойчивых E2E на критичные сценарии;
  • точечные UI-тесты для ключевых флоу.
  1. Автотесты не заменяют исследовательское мышление

Автоматизация идеально подходит для:

  • повторяемых регрессионных сценариев;
  • проверок инвариантов;
  • мониторинга ключевых контрактов и health-чеки.

Но она плохо:

  • изобретает новые гипотезы о возможных дефектах;
  • подмечает неочевидные несостыковки;
  • анализирует нетривиальные комбинации и "странные" пути, которые выбирает живой пользователь.

Поэтому:

  • исследовательское тестирование (exploratory),
  • анализ логов, метрик, инцидентов,
  • ревью требований и архитектуры

остаются незаменимыми.

  1. Технический долг в тестовой инфраструктуре

Автотесты тоже стареют:

  • меняются библиотеки, версии 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 через прокси).
  • В ответе на собеседовании достаточно отметить, что основной 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 по стандарту не идемпотентен,
    • но бизнес-логика делает конкретную операцию идемпотентной, чтобы избежать дублей при повторах.

Что важно показать на интервью:

  1. Идемпотентность — это:

    • свойство конечного состояния системы при повторе одинаковых запросов,
    • а не просто "методы, которые ничего не меняют".
  2. Формально идемпотентные методы:

    • GET, HEAD, PUT, DELETE, OPTIONS, TRACE.
  3. Практические нюансы:

    • Корректная реализация идемпотентности — ответственность сервиса:
      • DELETE не должен падать в неконсистентные состояния при повторных вызовах.
      • PUT должен задавать состояние, а не "добавлять сверху".
    • Для критичных операций (платежи, заказы):
      • добавляются идемпотентные механизмы поверх HTTP-методов,
      • чтобы повторные запросы клиента (например, при сетевых сбоях) не приводили к дублированию.

Сильный ответ:

  • точно формулирует определение,
  • знает список идемпотентных методов по спецификации,
  • понимает разницу между "безопасный" (safe) и "идемпотентный",
  • показывает, как идемпотентность используется и реализуется в реальных REST/микросервисах.

Вопрос 22. Как передать параметры в HTTP-методе GET?

Таймкод: 00:30:51

Ответ собеседника: правильный. Указывает, что параметры передаются в URL, что соответствует стандартной практике.

Правильный ответ:

В GET-запросах параметры передаются в URL. Основные способы:

  1. Путь (path parameters)

Используются для идентификации конкретного ресурса.

Примеры:

  • /users/123
  • /orders/2024/10/01

Обычно описываются в формате:

  • /resource/{id}
  1. 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 prosearch=iphone%2015%20pro
  • чувствительные данные (пароли, токены) в GET-параметрах передавать нельзя:
    • они попадают в логи, историю браузера, прокси.
  1. Комбинация 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.

Основные отличия:

  1. Семантика
  • PUT:

    • Означает полную замену ресурса по указанному идентификатору.
    • Тело запроса описывает новое состояние ресурса целиком.
    • Поля, не указанные в теле (при классическом трактовании), либо сбрасываются в значения по умолчанию, либо считаются удаленными (важно явно определить в API-спецификации).
    • Типичный URL: /resource/{id}.
  • PATCH:

    • Означает частичное обновление ресурса.
    • Тело запроса содержит только те поля (или операции), которые нужно изменить.
    • Остальные поля ресурса остаются без изменений.
    • Подходит для "точечных" апдейтов.
  1. Идемпотентность
  • PUT:

    • По спецификации должен быть идемпотентным.
    • Повторный PUT с теми же данными приводит к тому же состоянию ресурса.
  • PATCH:

    • Формально не обязан быть идемпотентным.
    • Но хороший дизайн стремится делать PATCH-операции идемпотентными:
      • "set field = value", а не "toggle" или "increment", если это не явно задокументировано.
    • Если PATCH реализован как "инструкции" (JSON Patch, RFC 6902), идемпотентность зависит от набора операций.
  1. Типичные практики использования
  • Когда использовать PUT:

    • Конфигурации и сущности с фиксированным набором полей.
    • Сценарии, где клиент всегда оперирует полной моделью объекта.
    • Пример:
      • управление настройками сервиса:
        • PUT /settings
        • тело содержит весь набор настроек.
  • Когда использовать PATCH:

    • Большие ресурсы, где передавать целиком весь объект ради одного поля неэффективно.
    • Высоконагруженные API, где важно минимизировать объем данных.
    • Частые частичные обновления:
      • смена email;
      • изменение статуса;
      • обновление одного атрибута.
  1. Примеры реализации в 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" }
]
  • Более гибко, но сложнее в реализации.
  1. Типичные ошибки и что важно сказать на интервью
  • Ошибка: использовать PUT как частичное обновление "по факту", без явной спецификации.

    • Это ломает ожидаемую семантику и идемпотентность.
  • Ошибка: использовать PATCH для "магических" операций без понятной модели (например, "немного поменять что-то внутри", не описывая контракт).

  • Хороший ответ:

    • четко разделяет:
      • PUT — полная замена ресурса, идемпотентен;
      • PATCH — частичное обновление, семантика определяется контрактом;
    • понимает влияние выбора метода на:
      • клиентские реализации,
      • кэширование,
      • предсказуемость API,
      • поддержку и эволюцию микросервисов.

Кратко:

  • PUT:

    • заменить ресурс целиком;
    • идемпотентен;
    • клиент обязан прислать полную модель (если не оговорено иначе).
  • PATCH:

    • изменить часть ресурса;
    • тело запроса содержит только изменения;
    • по стандарту не гарантированно идемпотентен, но в хорошо спроектированном API — предсказуем и безопасен.

Вопрос 24. Какие существуют группы HTTP статус-кодов ответа сервера?

Таймкод: 00:31:07

Ответ собеседника: неполный. Верно перечисляет:

  • 2xx — успешные;
  • 3xx — редиректы;
  • 4xx — клиентские ошибки;
  • 5xx — серверные ошибки. Упоминает 1xx как информационные, но ошибочно называет их устаревшими и недостоверными.

Правильный ответ:

HTTP-статус-коды делятся на пять классов по первой цифре. Все пять групп актуальны и стандартизированы (включая 1xx).

Классы статус-кодов:

  1. 1xx — Informational (информационные)
  • Означают, что запрос получен, и сервер продолжает обработку, но финальный ответ ещё не отправлен.
  • Используются в специфичных сценариях для оптимизации обмена и протокольных деталей.
  • Не являются устаревшими; их реже видно на уровне приложений, но они активно используются, например, в связке с Expect: 100-continue.

Примеры:

  • 100 Continue:
    • Клиент отправляет заголовки и ждет подтверждения, прежде чем слать большой body.
    • Сервер отвечает 100 Continue, если готов принять тело.
  • 101 Switching Protocols:
    • Используется при переключении протокола (например, для WebSocket).
  • 103 Early Hints:
    • Может быть использован для ранней отправки подсказок (link-заголовки для прилоада ресурсов).
  1. 2xx — Success (успешные)

Говорят, что запрос успешно обработан.

Ключевые:

  • 200 OK:
    • успешный запрос (GET/PUT/PATCH/DELETE и т.п.).
  • 201 Created:
    • ресурс успешно создан (чаще для POST).
  • 202 Accepted:
    • запрос принят к обработке, но не завершен (асинхронные операции).
  • 204 No Content:
    • успешно, но без тела ответа (например, DELETE, PUT без содержимого).
  1. 3xx — Redirection (перенаправление)

Указывают, что для завершения запроса клиенту нужно перейти по другому URL или выполнить дополнительный шаг.

Ключевые:

  • 301 Moved Permanently:
    • постоянный редирект.
  • 302 Found:
    • временный редирект (в исторической практике часто используется неправильно).
  • 303 See Other:
    • после POST/PUT перенаправляет на GET другого ресурса.
  • 307 Temporary Redirect / 308 Permanent Redirect:
    • более строгие варианты редиректов, сохраняющие метод и тело (для 307/308).
  1. 4xx — Client Error (ошибка клиента)

Проблема на стороне запроса: некорректные данные, права, формат, ресурс.

Ключевые:

  • 400 Bad Request:
    • некорректный запрос (валидация, формат).
  • 401 Unauthorized:
    • требуется аутентификация (или неверный токен).
  • 403 Forbidden:
    • доступ запрещен при корректной аутентификации.
  • 404 Not Found:
    • ресурс не найден.
  • 409 Conflict:
    • конфликт состояний (например, при дубликатах или версиях).
  • 422 Unprocessable Entity:
    • корректный по синтаксису запрос, но семантически неверен (часто для валидации).
  1. 5xx — Server Error (ошибка сервера)

Сервер не смог корректно обработать корректный запрос.

Ключевые:

  • 500 Internal Server Error:
    • общая ошибка сервера.
  • 502 Bad Gateway:
    • некорректный ответ от вышестоящего сервиса/прокси.
  • 503 Service Unavailable:
    • сервис временно недоступен (перегрузка, обслуживание).
  • 504 Gateway Timeout:
    • таймаут при обращении к внешнему/внутреннему сервису.

Инженерные акценты для реальных систем:

  • Корректное использование кодов:
    • уменьшает двусмысленность;
    • упрощает клиентам и другим сервисам обработку ошибок и ретраев.
  • В микросервисах:
    • важно различать 4xx и 5xx:
      • 4xx обычно не ретраится (ошибка запроса),
      • 5xx может быть кандидатом для автоматического retry с backoff.
  • Для 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.

  1. Структурирование коллекций и окружений
  • Коллекции:
    • группировка запросов по доменам и микросервисам: 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}}

Это позволяет:

  • одним и тем же набором запросов проверять разные стенды;
  • легко переключаться между окружениями.
  1. Переменные и 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.

  1. Автотесты в 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.

  1. 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-коллекции в часть регрессионного набора.
  1. Работа с аутентификацией и токенами

Уверенное владение включает:

  • автоматическое получение и обновление токенов в 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")}`
});
  1. Поддержка контрактов и работа с ошибками
  • Проверка не только happy-path, но и негативных сценариев:
    • 400/401/403/404/409/422;
    • валидация тела ответа при ошибках (код/сообщение).
  • Важно:
    • фиксировать контракты (структуру ответов) тестами;
    • чтобы любые breaking changes проявлялись сразу.
  1. Связка с 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:

  1. Переменные окружения (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
    • запускается против разных стендов просто сменой окружения.
  1. Переменные коллекции (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}}
  1. Глобальные переменные (Global variables)
  • Доступны во всех воркспейсах/коллекциях в рамках текущей рабочей среды Postman.
  • Использовать стоит осторожно:
    • удобно для временных значений во время отладки;
    • но в командной работе могут создавать путаницу.
  • Пример:
    • {{debug}} = true
    • или временно {{token}} при ручной работе.
  • Рекомендуется:
    • не хранить секреты и критичные значения в глобальных переменных;
    • не полагаться на них в командных коллекциях — лучше окружения и переменные коллекции.
  1. Локальные переменные (Local variables)
  • Существуют в рамках одного запроса (scope запроса).
  • Чаще задаются в скриптах:
    • let, const, var в Pre-request или Tests.
  • Не пересекаются между запросами (если не сохранить явно в env/collection/global).
  • Пример:
    let timestamp = Date.now();
    pm.environment.set("ts", timestamp);
  • Используются:
    • для вычислений, генерации данных, подготовки значений перед сохранением в более широкую область видимости.
  1. Data variables (переменные из Data-файлов в Runner)
  • Используются при запуске коллекций через Runner или Newman с CSV/JSON данными.
  • Позволяют параметризовать запросы набором входных данных (data-driven testing).
  • Пример:
    • CSV:

      email,password
      user1@example.com,Pass1
      user2@example.com,Pass2
    • В запросе:

      • {{email}}, {{password}} — берутся из текущей строки набора данных.
  1. Переменные в скриптах (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:

  1. Basic Auth

Суть:

  • Логин и пароль кодируются в Base64 и передаются в заголовке:
    • Authorization: Basic base64(username:password)

Особенности:

  • Используется для простых сервисов, внутренних API, технических учёток.
  • Обязательно только по HTTPS (иначе credentials утекут).

В Postman:

  • Во вкладке Authorization выбираем:
    • Type: Basic Auth
    • Вводим Username / Password.
  • Postman сам сформирует заголовок.
  1. Bearer Token

Суть:

  • В заголовке передается токен доступа:
    • Authorization: Bearer <token>
  • Токен может быть:
    • произвольной строки (opaque token),
    • JWT (JSON Web Token) — просто формат токена, а не отдельный метод авторизации.

Использование:

  • Наиболее распространённый вариант в современных REST/микросервисах.
  • Токен обычно выдается:
    • отдельным auth-сервисом;
    • по OAuth2/OpenID Connect флоу;
    • или через собственный login endpoint.

В Postman:

  • Способы:
    1. Authorization → Type: Bearer Token → вставить токен.
    2. Хранить токен в переменной:
      • Authorization: Bearer {{access_token}}
      • токен получать/обновлять в Pre-request Script или отдельным запросом.
  1. 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.
  1. 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 в запросы.
  1. OpenID Connect (OIDC)

Суть:

  • Надстройка над OAuth2 для аутентификации.
  • Возвращает:
    • ID Token (часто JWT) с информацией о пользователе.
  • Используется для:
    • SSO;
    • федеративной аутентификации.

В контексте API:

  • ID Token обычно не используется для доступа к ресурсам,
  • для этого есть Access Token (часто тоже Bearer).
  • На уровне Postman:
    • работа схожа с OAuth2 (получение токена и подстановка в заголовок).
  1. Cookie-based / Session ID

Суть:

  • Классический веб-подход:
    • после логина сервер выдает cookie с session id;
    • браузер автоматически отправляет cookie дальше.
  • В API:
    • иногда используется в админках и внутренних UI.

В Postman:

  • Либо логин-запрос, который устанавливает cookie;
  • Либо ручная настройка cookie во вкладке Cookies.
  • Далее Postman будет слать cookie в последующих запросах.
  1. Custom token / HMAC / подписи запросов

Суть:

  • Свои схемы:
    • HMAC-подписи:
      • заголовки вида X-Signature, X-Timestamp, X-Client-Id;
    • Signed URL;
    • комбинации ключ + подпись тела запроса.

Использование:

  • Финтех, интеграции с внешними партнерами, повышенные требования безопасности.

В 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>.
  • 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-разработчики могли быстро их устранить.

Ключевые вкладки и практики.

  1. Elements (DOM + стили)

Для чего используется:

  • Проверка:
    • корректной разметки (семантика, вложенность, уникальность id);
    • применения CSS-классов;
    • адаптивности (responsive) под разные разрешения;
    • корректности отображения скрытых/динамических элементов.
  • Поиск проблем:
    • перекрытие элементов;
    • некликабельные кнопки;
    • элементы вне видимой области;
    • некорректная работа :hover/:active/:focus.

Практика:

  • Инспекция "невидимых" или перекрытых элементов:
    • если по UI "кнопка не нажимается", в Elements проверяем:
      • кто сверху;
      • есть ли pointer-events: none;;
      • z-index, позиционирование.
  • Проверка адаптивности:
    • через Device Toolbar:
      • разные устройства/ширины;
      • ориентация экрана;
      • retina/non-retina.
  1. 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,
    • это экономит десятки минут споров.
  1. Console

Используем для:

  • Анализа ошибок JavaScript:
    • исключения, stack trace;
    • ошибки инициализации компонентов;
    • CORS, CSP, mixed content.
  • Предупреждений:
    • deprecated API;
    • проблемы с безопасностью.
  • Ручного выполнения JS:
    • проверка наличия глобальных объектов и переменных;
    • быстрая диагностика состояния приложения (например, текущее состояние хранилища).

Практика:

  • Если UI "не реагирует":
    • первым делом смотрим Console:
      • часто там красный stack trace, указывающий прямо в проблемное место.
  • Для backend-разработчиков:
    • передаём в задачу:
      • текст ошибки из Console,
      • контекст (шаги воспроизведения),
      • скрин.
  1. Application / Storage

Ключевая вкладка для:

  • Cookies:
    • проверка:
      • установки/пролонгации сессий;
      • флагов Secure, HttpOnly, SameSite.
  • LocalStorage / SessionStorage:
    • проверка:
      • хранения токенов (важно: небезопасно хранить чувствительные данные);
      • кэша фронта;
  • IndexedDB:
    • оффлайн-данные, кеширование.
  • Cache / Service Workers:
    • PWA-сценарии;
    • отладка проблем из-за закешированного фронта.

Практика:

  • Проверка безопасности:
    • JWT или access-токен в localStorage без флагов — повод для security-замечания.
  • Диагностика:
    • "помогает только очистка кэша" → смотрим Service Worker, кеш, localStorage.
  1. Network Conditions, Throttling и эмуляция устройств

Используем для:

  • Эмуляции:
    • медленного 3G, высокой латентности;
    • offline-сценариев;
  • Проверки:
    • как UI ведёт себя при плохой сети:
      • есть ли лоадеры/спиннеры;
      • корректные сообщения об ошибках;
      • нет ли бесконечной "висящей" загрузки.

Практический пример:

  • Тестируем:
    • "поведение при таймауте backend".
  • Действия:
    • включаем "Slow 3G" или вручную увеличиваем latency;
    • проверяем:
      • корректный показ ошибки;
      • отсутствие зависших UI-состояний.
  1. Подмена, инспекция и воспроизведение запросов

DevTools позволяют:

  • копировать запрос как curl:
    • удобно для backend-диагностики и автотестов;
  • редактировать и повторно отправлять запрос (в некоторых браузерах или через дополнительные инструменты);
  • использовать Breakpoints в XHR/fetch (через Sources) для сложной отладки.

Это помогает:

  • воспроизводить баги без кликов в UI;
  • переносить сценарии в Postman или автотесты;
  • чётко фиксировать контрактные проблемы.
  1. 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.

  1. Отслеживание всех запросов приложения
  • При открытой вкладке Network фиксируются:
    • XHR/fetch запросы (основные для SPA и API);
    • document (загрузка страниц);
    • JS/CSS ресурсы;
    • WebSocket, EventSource;
    • изображения, шрифты и др.
  • Можно фильтровать по типам:
    • XHR/Fetch — для REST/gRPC-over-HTTP/GraphQL;
    • JS/CSS — для диагностики загрузки ресурсов.

Практика:

  • Воспроизводим баг → смотрим список запросов:
    • были ли вызваны нужные эндпоинты,
    • нет ли лишних/дублирующихся запросов.
  1. Анализ запросов: метод, URL, параметры

Для каждого запроса важно проверить:

  • HTTP-метод:
    • соответствует ли контракту (GET/POST/PUT/PATCH/DELETE);
  • URL:
    • правильность пути и query-параметров;
  • соответствие REST-семантике и спецификации API.

Примеры:

  • Проверка фильтрации:
    • GET /api/v1/orders?status=active&limit=50
  • Проверка пагинации:
    • корректная передача page, page_size.

Если UI ведет себя странно:

  • проверяем, корректно ли формируются запросы (часто проблема именно на фронте).
  1. Заголовки запросов (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.
  1. Тело запроса (Request Payload)

Для POST/PUT/PATCH важно:

  • структура JSON;
  • обязательные поля;
  • типы значений;
  • корректность кодировки.

Примеры проверок:

  • UI отправляет:
    • пустые поля вместо null;
    • строки вместо чисел;
    • лишние/устаревшие поля.
  • Это помогает:
    • четко показать, что проблема на фронте (формирует неверный payload),
    • или наоборот — что backend некорректно обрабатывает корректный payload.
  1. Анализ ответов (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"}:
      • фронт не отображает нормальное сообщение — баг фронта.
  • При 5xx:
    • фиксируем точный ответ и условия — явный кандидата на баг backend.
  1. Вкладка Timing и производительность

Для каждого запроса:

  • DNS, TCP, SSL, TTFB (time to first byte), content download.
  • Помогает:
    • отличить сетевые проблемы от проблем backend;
    • выявить медленные эндпоинты.

Практика:

  • Если UI "тормозит":
    • смотрим:
      • один запрос 2–5 секунд → кандидаты на оптимизацию в backend/БД;
      • много лишних запросов → проблема дизайна фронта.
  1. Использование Application для проверки сохраненных данных

В связке с Network:

  • Cookies:
    • токены сессий;
    • флаги безопасности (HttpOnly, Secure, SameSite).
  • Local Storage / Session Storage:
    • хранение токенов, флагов, конфигураций.
  • Проверяем:
    • корректно ли обновляются токены после логина/логаута;
    • нет ли утечек/небезопасного хранения.
  1. Практический подход при тестировании и баг-репортах

Хороший сценарий использования 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, внешние провайдеры).

Ниже — последовательный инженерный алгоритм диагностики.

  1. Проверка запроса на фронте (DevTools → Network)

Сначала убеждаемся, что фронт корректно формирует и отправляет запрос.

Проверяем:

  • Уходит ли запрос при нажатии "Войти":
    • если нет:
      • проблема во фронте: JS-ошибка, неправильный handler, отключенная кнопка, валидация не пускает запрос.
  • Метод и URL:
    • совпадает ли с реальным API-эндпоинтом авторизации:
      • POST /api/v1/auth/login (пример);
      • нет ли опечаток, лишних слэшей, неправильного пути.
  • Тело запроса (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);
    • тело ошибки:
      • код/сообщение, дающие подсказку.

Если запрос не совпадает с контрактом — это баг фронта, даже если логин/пароль в базе корректны.

  1. Очистка локального состояния (cookies, storage, кеш)

Частая причина проблем — "грязное" состояние:

  • протухшие/битые cookies;
  • старые токены в localStorage/SessionStorage;
  • конфликт старой версии фронта из кеша.

Действия:

  • во вкладке Application:
    • удалить cookies для домена;
    • очистить LocalStorage/SessionStorage;
  • во вкладке Network:
    • отключить кэш и повторить запрос;
  • проверить в режиме инкогнито / другом браузере.

Если после очистки авторизация работает:

  • вероятна проблема с:
    • некорректной обработкой старых сессий;
    • миграцией токенов;
    • кешированием фронта.
  1. Проверка 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.
  1. Диагностика на 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.
  1. Проверка окружения и конфигурации

Важно убедиться:

  • что UI и backend смотрят на одно окружение:
    • часто фронт настроен на api-dev, а вы проверяете пользователей из api-stage;
  • что нет конфликтов доменов и CORS:
    • не режутся ли запросы прокси, шлюзом, WAF;
  • что auth-сервис корректно настроен:
    • секреты (JWT secret, OAuth client_id/secret);
    • URL внешних identity-провайдеров.
  1. Принципиальный подход (как должен звучать сильный ответ)

Последовательная стратегия:

    1. Проверяю во вкладке Network:
    • уходит ли запрос,
    • корректны ли URL, метод, заголовки, тело,
    • что возвращает сервер (статус/ответ).
    1. Очищаю локальное состояние:
    • cookies, storage, кеш, пробую в другом браузере.
    1. Проверяю backend напрямую:
    • через Postman/curl — тот же запрос, те же данные.
    1. Если в Postman ок, а в UI нет:
    • баг на фронте:
      • неверный контракт,
      • неверная обработка ответа,
      • лишние данные/заголовки.
    1. Если и через Postman ошибка:
    • иду в сторону backend:
      • логи, БД, конфиги, внешние auth-сервисы.
    1. Документирую:
    • конкретный запрос/ответ,
    • окружение,
    • условия воспроизведения,
    • чтобы разработчики могли быстро воспроизвести.

Такой подход демонстрирует умение не "гадать", а инженерно локализовывать проблему между слоями системы, используя 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 — это отдельный кейс.

  1. 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 не попадут.

Смысл:

  • "Пересечение множеств" по условию.
  1. 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.
  1. 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, поменяв местами таблицы для читаемости.
  1. 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 обычно не используется: нужно указывать вид.
  1. CROSS JOIN

Декартово произведение: каждая строка левой таблицы сочетается с каждой строкой правой.

SELECT u.name, o.id AS order_id
FROM users u
CROSS JOIN orders o;

Если:

  • 3 users и 3 orders → 9 строк.

Применение:

  • генерация комбинаций;
  • редко в бизнес-логике, чаще в спец задачах или случайно (забыли ON в JOIN).

Опасность:

  • может резко взорвать размер результата, если применен по ошибке.
  1. 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;

Применение:

  • иерархии;
  • связи внутри одной сущности (родитель–потомок).
  1. Важные нюансы и типичные ошибки
  • Условие соединения (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;
  • Здесь логика верна: мы специально ищем строки, где связанного заказа нет.
  1. Производительность и практика
  • 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, но важно понимать различия по смыслу и читаемости.

  1. 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) будет только одна.
  1. 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 не подходит, потому что нам нужны и уникальные статусы, и агрегированные данные.

  1. Практические моменты и рекомендации
  • Использовать DISTINCT:

    • когда цель — убрать дубликаты и получить уникальный набор значений/комбинаций;
    • особенно в подзапросах:
      • например, список уникальных пользователей, совершавших заказы:

        SELECT DISTINCT user_id
        FROM orders;
  • Использовать GROUP BY:

    • когда одновременно с уникальностью требуется агрегация;
    • либо в редких случаях, когда в конкретной команде принят такой стиль (но это спорно, DISTINCT более выразителен).
  1. Подводные камни
  • 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;
  1. Использование в контексте приложений и 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 фильтрует уже сгруппированные результаты (агрегаты).

Последовательность (упрощенно) выполнения типичного запроса:

  1. FROM / JOIN — формирование набора данных
  2. WHERE — фильтрация строк
  3. GROUP BY — группировка
  4. HAVING — фильтрация групп
  5. SELECT — вычисление выражений и вывод
  6. ORDER BY / LIMIT

Отсюда следуют правила использования.

  1. 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 работают уже по отфильтрованному набору.
  1. 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;
  1. Типичная ошибка и как её избежать

Ошибка:

  • Пытаться использовать агрегаты в WHERE:
-- Ошибка:
SELECT user_id, COUNT(*)
FROM orders
WHERE COUNT(*) > 3
GROUP BY user_id;

Так нельзя, потому что COUNT(*) ещё не посчитан на этапе WHERE.

Правильно:

  • Для условий по "обычным" полям (status, date, type) — WHERE.
  • Для условий по агрегатам (COUNT, SUM, AVG и т.п.) — HAVING.
  1. Практический паттерн (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 FIRST
        • ORDER BY field DESC NULLS LAST
  • Это важно для предсказуемости выдачи при сортировке по полям, где возможны 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 используется для текстового поиска по шаблону в строковых полях. Он позволяет находить строки, которые частично совпадают с заданным образцом, без использования полнотекстового поиска.

Базовые маски:

  • % — произвольная последовательность символов (включая пустую).
  • _ — ровно один любой символ.

Примеры использования:

  1. Поиск по подстроке (содержит):
SELECT *
FROM users
WHERE last_name LIKE '%Иванов%';

Найдет:

  • "Иванов"
  • "Иванова"
  • "Петров-Иванов" и т.п.
  1. Поиск по началу строки:
SELECT *
FROM users
WHERE last_name LIKE 'Иван%';

Найдет:

  • "Иванов"
  • "Иваненко"
  • "Иванов-Сидоров" и т.п.
  1. Поиск по окончанию строки:
SELECT *
FROM users
WHERE last_name LIKE '%ов';

Найдет:

  • "Иванов"
  • "Петров"
  • "Сидоров" и т.п.
  1. Использование _ для одного символа:
SELECT *
FROM users
WHERE code LIKE 'AB_3';

Найдет:

  • "ABA3", "ABB3", "ABX3" но не "AB33" или "AB123".
  1. Экранирование специальных символов

Если нужно искать символы % или _ буквально, используют ESCAPE:

SELECT *
FROM logs
WHERE message LIKE '%100\%%' ESCAPE '\';

Ищем строки, содержащие "100%".

  1. Особенности и практические моменты:
  • Регистр:
    • В PostgreSQL LIKE обычно регистрозависим, для нечувствительного поиска используют ILIKE.
    • В MySQL чувствительность зависит от collation.
  • Производительность:
    • Условие вида 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 и т.д.), но общие идеи похожи.

Базовые сценарии использования:

  1. Промежуточные результаты сложных запросов

Когда запрос становится слишком громоздким (несколько джойнов, подзапросы, фильтрация), временная таблица:

  • упрощает читаемость;
  • позволяет повторно использовать результаты;
  • иногда улучшает план выполнения.

Пример (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 дней во временную таблицу;
  • индексим временную таблицу под наши запросы;
  • далее считаем агрегаты уже по уменьшенному набору данных.
  1. Повторное использование тяжелых выборок

Если один и тот же сложный набор данных нужен в нескольких частях процедуры/скрипта:

  • вместо дублирования подзапросов:
    • один раз формируем временную таблицу,
    • затем обращаемся к ней.

Это может:

  • уменьшить нагрузку на БД;
  • сделать планы более предсказуемыми.
  1. Разбор и нормализация входных данных

Когда:

  • загружаем данные "как есть" (bulk load, CSV, файловый импорт);
  • нужно:
    • почистить;
    • проверить;
    • сопоставить с основными справочниками;
    • только потом залить в боевые таблицы.

Сценарий:

  • временная таблица как staging-area:
    • валидируем данные;
    • пишем ошибки/отчеты;
    • корректные данные переносим в основные таблицы.
  1. Изоляция данных в рамках сессии или транзакции
  • Данные во временных таблицах:
    • не видны другим сессиям;
    • удобно для:
      • персонализированных расчетов;
      • сложных отчётов на лету;
      • хранимых процедур.

Реализация в разных СУБД (кратко):

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 оба используются для хранения строк, но различаются способом хранения, поведением и типичными сценариями применения. Понимание этих отличий важно для корректного дизайна схемы и производительности.

Основные различия:

  1. Длина и хранение
  • CHAR(N):

    • Фиксированная длина.
    • Для каждого значения в столбце отводится ровно N символов.
    • Если строка короче N:
      • она дополняется пробелами до длины N (логически или физически — зависит от СУБД).
    • Пример:
      • CHAR(5):
        • 'AB' хранится как 'AB '.
    • Подходит для значений предсказуемой длины:
      • коды стран (ISO),
      • коды валют,
      • фиксированные статусы, двухсимвольные коды и т.п.
  • VARCHAR(N):

    • Переменная длина.
    • Хранит строки длиной от 0 до N символов.
    • Место занимает пропорционально фактической длине + служебная информация о длине.
    • Пример:
      • VARCHAR(255):
        • 'AB' хранится как длина=2 + 'AB', без добивки пробелами.
    • Подходит для:
      • имен, email, адресов, описаний и т.п.
  1. Поведение при сравнении и пробелах

Особенности зависят от СУБД, но в классическом SQL и ряде реализаций:

  • Для CHAR:
    • завершающие пробелы при сравнении часто игнорируются.
    • 'AB' и 'AB ' считаются равными.
  • Для VARCHAR:
    • завершающие пробелы обычно учитываются (зависит от collation/настроек).

Важно:

  • Это может влиять на запросы, уникальные ключи и логику:
    • CHAR(5) с 'AB' и 'AB ' может трактоваться как один и тот же ключ.
  1. Производительность и применение

Исторически:

  • CHAR:
    • мог быть быстрее при фиксированной длине, т.к. проще адресация и сканирование.
  • В современных СУБД:
    • разница меньше, но подходы всё еще важны.

Практические рекомендации:

  • Использовать CHAR:
    • для строго фиксированной длины и строго формализованных значений:
      • country_code CHAR(2),
      • currency_code CHAR(3),
      • статус, если он всегда ровно 1 символ и т.п.
  • Использовать VARCHAR:
    • почти во всех остальных случаях:
      • имена, логины, email, пути, описания и т.д.
  1. Ограничения длины
  • CHAR(N) и VARCHAR(N):
    • N — максимальная длина в символах (в некоторых СУБД — в байтах, важно для Unicode).
  • При вставке строки длиннее N:
    • корректная СУБД:
      • либо выдаст ошибку,
      • либо обрежет (в зависимости от настроек, ANSI/strict mode).
    • Это уже другая история, не связанная с "короткими" строками для CHAR.
  1. Пример с выбором типов

Неплохая схема:

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, потому что длина варьируется.
  1. Контекст для инженеров backend

При работе из приложения (например, Go):

  • Типы CHAR и VARCHAR для драйвера обычно мапятся в string.
  • Нюансы:
    • trailing-пробелы в CHAR полях:
      • могут неожиданно "приезжать" в значения, если СУБД не обрезает их на чтении.
    • Лучше явно trim-ить или правильно понимать поведение конкретной СУБД.

Пример чтения в 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)".