UI-тест который кликает "Оформить заказ" и видит экран подтверждения не говорит тебе был ли заказ реально записан в базу данных. SQL говорит. Статья разбирает пять паттернов запросов которые покрывают 80% QA-работы с базами данных: SELECT с WHERE, JOIN, COUNT и GROUP BY, проверки NULL, и как запускать запросы напрямую из Playwright-тестов для верификации сохранения данных.
Почему QA-инженерам нужен SQL
UI-тесты верифицируют что видит пользователь. SQL верифицирует что реально произошло.
Когда тест кликает "Оформить заказ" и видит экран подтверждения, ты знаешь что UI ответил. Не знаешь сохранён ли заказ. Не знаешь правильный ли статус выставлен. Не знаешь правильно ли написан внешний ключ. Единственный способ верифицировать это: смотреть в базу данных напрямую.
Случаи использования SQL в QA:
- Верифицировать что данные сохранены после UI-действия (отправка формы, загрузка файла, платёж)
- Подготовить тестовые данные напрямую вместо 10 кликов через UI
- Проверить данные которые UI не показывает (audit-логи, внутренние флаги, мягко удалённые записи)
- Воспроизвести баги проверяя в каком состоянии была база когда что-то сломалось
- Валидировать миграции после деплоя изменившего схему
Когда вакансия говорит "требуется опыт SQL": вот о чём идёт речь. Не хранимые процедуры, не оптимизация запросов, не администрирование базы данных. SELECT-запросы с условиями.
Инструмент: любой SQL-клиент
Знать конкретный продукт базы данных не нужно. Синтаксис запросов почти идентичен в PostgreSQL, MySQL и SQLite. Для практики:
- TablePlus (Mac/Windows): чистый интерфейс, бесплатного уровня достаточно
- DBeaver: бесплатный, работает с любым типом базы данных
- psql: командная строка PostgreSQL, всегда доступна
- Любой Playwright-тест с пакетом
pgилиmysql2тоже может запускать запросы напрямую
Примеры ниже основаны на схеме базы данных lab.becomeqa.com.
Паттерн 1: SELECT
Самое частое что будешь делать:
-- Получить всех пользователей
SELECT * FROM users;
-- Получить конкретные столбцы
SELECT id, email, created_at FROM users;
-- Получить одного пользователя по email
SELECT * FROM users WHERE email = 'admin@becomeqa.com';
-- Получить пользователей созданных за последние 7 дней
SELECT * FROM users WHERE created_at > NOW() - INTERVAL '7 days';SELECT * получает каждый столбец. SELECT id, email получает только указанные столбцы. WHERE фильтрует строки. Это 90% того что нужно.
В QA-контексте
-- После теста регистрации верифицировать что пользователь создан
SELECT id, email, role, is_active
FROM users
WHERE email = 'testuser_1234567@test.com';Если возвращает строку: пользователь сохранён. Если ничего: регистрация тихо упала хотя UI показал успех.
Паттерн 2: WHERE с условиями
Комбинирование условий:
-- AND: оба условия должны быть истинны
SELECT * FROM items
WHERE status = 'completed' AND user_id = 42;
-- OR: хотя бы одно условие должно быть истинно
SELECT * FROM items
WHERE status = 'pending' OR status = 'in_progress';
-- IN: совпадение с любым значением в списке
SELECT * FROM items
WHERE status IN ('pending', 'in_progress', 'completed');
-- NOT: исключить строки
SELECT * FROM items WHERE status != 'deleted';
-- LIKE: частичное совпадение строк (% - wildcard)
SELECT * FROM users WHERE email LIKE '%@test.com';
-- IS NULL: проверка на отсутствие значений
SELECT * FROM items WHERE deleted_at IS NULL;
SELECT * FROM items WHERE deleted_at IS NOT NULL;В QA-контексте
-- Верифицировать что мягкое удаление сработало (deleted_at должен быть выставлен)
SELECT id, title, deleted_at
FROM items
WHERE id = 99;
-- Найти все тестовые данные для очистки после прогона
SELECT * FROM users WHERE email LIKE '%testuser_%@test.com';Паттерн 3: JOIN
Реальные данные живут в нескольких таблицах. Travel-элемент принадлежит пользователю. Заказ принадлежит клиенту и содержит продукты. JOIN нужен чтобы видеть полную картину.
-- Базовый JOIN: элементы с email их владельца
SELECT items.id, items.title, items.status, users.email
FROM items
JOIN users ON items.user_id = users.id;
-- Фильтрация результата JOIN
SELECT items.id, items.title, users.email
FROM items
JOIN users ON items.user_id = users.id
WHERE users.email = 'admin@becomeqa.com';Паттерн всегда такой:
SELECT [нужные столбцы]
FROM [основная таблица]
JOIN [связанная таблица] ON [как они соединяются]
WHERE [опциональный фильтр]В QA-контексте
-- После добавления элемента как admin, верифицировать что он связан с правильным пользователем
SELECT items.title, items.status, users.email AS owner
FROM items
JOIN users ON items.user_id = users.id
WHERE items.title = 'Tokyo'
ORDER BY items.created_at DESC
LIMIT 1;Паттерн 4: COUNT и агрегатные функции
Когда нужны числа, а не строки:
-- Сколько пользователей?
SELECT COUNT(*) FROM users;
-- Сколько активных пользователей?
SELECT COUNT(*) FROM users WHERE is_active = true;
-- Сколько элементов по каждому статусу?
SELECT status, COUNT(*) AS total
FROM items
GROUP BY status;
-- Самая поздняя дата создания элемента
SELECT MAX(created_at) FROM items;
-- Общее количество элементов по пользователю
SELECT user_id, COUNT(*) AS item_count
FROM items
GROUP BY user_id
ORDER BY item_count DESC;В QA-контексте
-- После теста массового импорта верифицировать что создано правильное количество записей
SELECT COUNT(*) FROM items WHERE created_at > '2026-05-15 10:00:00';
-- Верифицировать изоляцию тестовых данных (нет перекрёстного заражения между прогонами)
SELECT user_id, COUNT(*) FROM items GROUP BY user_id;Паттерн 5: ORDER BY и LIMIT
Управление какие строки получаешь и в каком порядке:
-- Самые недавно созданные элементы первыми
SELECT * FROM items ORDER BY created_at DESC;
-- Самые старые первыми
SELECT * FROM items ORDER BY created_at ASC;
-- Только 5 самых недавних
SELECT * FROM items ORDER BY created_at DESC LIMIT 5;
-- Страница 2 результатов (строки 11–20)
SELECT * FROM items ORDER BY id LIMIT 10 OFFSET 10;В QA-контексте
-- После того как тест создал элемент, взять только что созданный
SELECT * FROM items
WHERE user_id = 42
ORDER BY created_at DESC
LIMIT 1;Полный верификационный запрос
Флоу теста: пользователь входит, добавляет travel-элемент с названием "Paris", помечает его как "Completed". Как верифицировать полную операцию в SQL:
SELECT
items.id,
items.title,
items.status,
items.created_at,
users.email AS owner
FROM items
JOIN users ON items.user_id = users.id
WHERE items.title = 'Paris'
AND items.status = 'completed'
AND users.email = 'admin@becomeqa.com'
ORDER BY items.created_at DESC
LIMIT 1;Если возвращает строку: весь флоу отработал от начала до конца. Если ничего: что-то тихо упало между входом и обновлением статуса.
Использование SQL в Playwright-тестах
Можно запускать SQL напрямую из тест-кода используя клиентскую библиотеку базы данных:
import { test, expect } from '@playwright/test';
import { Client } from 'pg'; // npm install pg
test('item is saved to database after creation', async ({ page }) => {
// UI-действие
await page.goto('/');
// ... вход, добавление элемента 'Tokyo' ...
// Верификация в базе данных
const db = new Client({ connectionString: process.env.DATABASE_URL });
await db.connect();
const result = await db.query(
'SELECT * FROM items WHERE title = $1 ORDER BY created_at DESC LIMIT 1',
['Tokyo']
);
expect(result.rows.length).toBe(1);
expect(result.rows[0].status).toBe('planned');
await db.end();
});Мощный паттерн: UI-действие плюс верификация в базе в одном тесте. UI-тест доказывает что приложение ответило корректно; запрос к базе данных доказывает что данные реально сохранились.
Частые ошибки
Использование SELECT в продакшн-тестах. Нормально для отладки, но в автотестах называй столбцы явно. Когда столбец добавляется или удаляется,SELECT скрывает изменение.
Забыть WHERE при DELETE. Если очищаешь тестовые данные через DELETE FROM items WHERE email LIKE '%test%': всегда перепроверяй WHERE перед запуском. DELETE FROM items без WHERE удаляет всё.
Не использовать параметризованные запросы в коде. Никогда не строй SQL-строки конкатенацией пользовательского ввода. Используй плейсхолдеры $1 как показано выше для защиты от SQL-инъекции.
Читать устаревшие данные. В некоторых базах данных изоляция транзакций означает что нужно выполнить COMMIT транзакции прежде чем другое соединение увидит изменения. Если тест пишет данные и затем запрашивает их из другого соединения: убедись что транзакция завершена.
Что пока не нужно
- Подзапросы
- Оконные функции (ROW_NUMBER, RANK)
- CTE (конструкции WITH)
- Хранимые процедуры и функции
- Индексы и планирование запросов
- Проектирование схемы и нормализация
Всё это важно для разработчиков баз данных. Для QA задача: читать данные. Пять паттернов выше: вся работа в 95% случаев.
FAQ
На какой базе данных учить SQL?
PostgreSQL. Самый распространённый в современных веб-приложениях, лучший инструментарий, синтаксис достаточно стандартный чтобы переключиться на MySQL или SQLite за минуты. Установи TablePlus, подключись к любому PostgreSQL-инстансу, практикуйся там.
Можно ли практиковать SQL без реального приложения?
Да. Сайты sqlfiddle.com и db-fiddle.com позволяют создавать таблицы и запускать запросы прямо в браузере без установки. Создай таблицы users и items, вставь несколько строк, практикуй пять паттернов выше.
Писать SQL-тесты или использовать только для отладки?
Обе цели. SQL незаменим для ручной отладки. Когда не понимаешь почему тест падает: проверяй базу данных. И ценен в автотестах для верификации данных которую UI не может обеспечить. Начинай с отладки, добавляй автоматизированные database assertions когда освоишься с запросами.
В чём разница между SQL-базами данных?
Для QA-целей почти ни в чём. PostgreSQL использует $1 для параметров, MySQL использует ?, но синтаксис SELECT/WHERE/JOIN идентичен. Пять паттернов выше работают во всех трёх.