Урок по треку API и бэкенд для тестировщика 8 мин

Тестирование БД через SQL за 8 минут

Проверка данных после запроса, дубли, сироты, расхождения сумм, транзакции и тестовые данные.

База для тестировщика это второй источник правды после API. На собесе на QA почти всегда есть пара SQL-вопросов, а иногда и задачи: «найди дубли», «найди заказы без оплаты». Здесь разберём запросы, которые реально пишут в работе.

Зачем лезть в базу

  • Проверить результат. API вернуло 201, а в базе не тот статус или сумма.
  • Найти дефекты в данных. Дубли, сироты, расхождения сумм.
  • Подготовить данные. Заказ, который «висит в оплате третий день», через интерфейс не создашь.
  • Разобрать баг. По времени и id видно, на каком шаге данные испортились.

Проверка после запроса

SELECT email, name, status, created_at
FROM users
WHERE id = 57;

Конкретная запись по ключу и конкретные поля. SELECT * по всей таблице ничего не проверяет, а count(*) соврёт при параллельных тестах.

NULL

NULL значит «неизвестно», и сравнение с ним через = даёт unknown, а не true. WHERE такие строки отбрасывает.

WHERE deleted_at IS NULL      -- правильно
WHERE deleted_at = NULL       -- всегда 0 строк, тест молча проходит

Ещё две ловушки: x NOT IN (подзапрос), где в подзапросе есть NULL, всегда пуст. SUM по пустому набору даёт NULL, поэтому сравнение total <> SUM(...) для заказа без позиций тоже вернёт unknown. Лечится COALESCE(SUM(...), 0).

Типовые проверки

Дубли. Условие на агрегат пишется в HAVING, не в WHERE.

SELECT lower(email) AS email, count(*) AS cnt
FROM users
GROUP BY lower(email)
HAVING count(*) > 1;

Сироты (ссылка в пустоту). LEFT JOIN сохраняет все строки слева, у ненайденных справа колонки равны NULL.

SELECT o.id
FROM orders o
LEFT JOIN users u ON u.id = o.user_id
WHERE o.user_id IS NOT NULL AND u.id IS NULL;

С INNER JOIN этот запрос всегда пуст: он выбрасывает именно те строки, которые мы ищем. Аналог через NOT EXISTS безопасен к NULL.

Отсутствие связанного. Оплаченный заказ без успешного платежа:

SELECT o.id
FROM orders o
WHERE o.status IN ('paid', 'shipped')
  AND NOT EXISTS (
    SELECT 1 FROM payments p
    WHERE p.order_id = o.id AND p.status = 'success'
  );

Если писать через LEFT JOIN, условие на статус платежа ставят в ON. В WHERE оно отфильтрует заказы без платежей, и дефект не найдётся.

Сходятся ли суммы. Итог заказа против позиций, оплаты против итога. Для частичных сумм удобен SUM(...) FILTER (WHERE ...).

Логика статусов и дат. updated_at < created_at, удалённый, но активный пользователь, отгруженный заказ без оплаты.

Последнее по группе. Статус последнего платежа по каждому заказу: ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY created_at DESC) и фильтр rn = 1, или в PostgreSQL DISTINCT ON.

Транзакции и тестовые данные

ACID: атомарность, согласованность, изоляция, надёжность. Для тестов важно:

  • Незафиксированные изменения видит только своё соединение. Если тест вставил строку и не сделал COMMIT, API её не увидит. В PostgreSQL грязного чтения нет вообще.
  • BEGIN ... ROLLBACK бесследно убирает изменения, но работает, только если тест и проверка в одном соединении.
  • Для API-тестов данные лучше создавать через API или фикстуры с уникальными метками и удалять по ним. Удаление «всего за последний час» снесёт чужие данные на общем стенде.
  • Полная изоляция: своя база в контейнере на прогон (Testcontainers) с миграциями и сидами.

Ограничения и миграции

Проверка «email уже занят» в коде не спасает от двух параллельных регистраций. Защищает UNIQUE в базе, ссылки держит FOREIGN KEY, правила CHECK и NOT NULL. Отсутствие ограничений на критичных полях стоит поднимать как риск.

Миграцию проверяют не на пустой базе, а на данных, похожих на прод: что будет с существующими NULL, сколько займёт, не заблокирует ли таблицу, как откатить.

Безопасность и этика

На общих стендах пишите только в свои данные. В проде, если доступ вообще есть, только чтение. Копию прода для тестов берут обезличенной: персональные данные в тестовой среде нарушают 152-ФЗ.

Что спросят на собесе

  • Зачем тестировщику SQL? Приведите пример запроса из работы.
  • Чем LEFT JOIN отличается от INNER JOIN?
  • Как найти дубли? Чем WHERE отличается от HAVING?
  • Почему = NULL не работает и чем опасен NOT IN?
  • Как вы готовите и убираете тестовые данные?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.