Modern QA2026SELECT-запросы для верификации
Join

Course15 SQL & Database Testing

Foundations · Chapter 15

SELECT-запросы для верификации

Updated Jul 2026

После выполнения действия через UI или API проверьте результат на уровне базы данных. База данных — источник истины: если UI говорит «заказ создан», но в базе нет записи, данные на самом деле не были сохранены.

Базовые верификационные запросы

-- Verify user was stored correctly after API creation
SELECT id, name, email, role, created_at
FROM users
WHERE email = 'newuser@test.com';

-- Verify soft-delete worked (deleted_at should be set, not NULL)
SELECT id, email, deleted_at
FROM users
WHERE email = 'removed@test.com'
  AND deleted_at IS NOT NULL;

-- Verify order total matches line items (returns rows only if inconsistent)
SELECT o.id, o.total, SUM(li.quantity * li.unit_price) AS calculated_total
FROM orders o
JOIN line_items li ON li.order_id = o.id
WHERE o.id = 42
GROUP BY o.id, o.total
HAVING o.total != SUM(li.quantity * li.unit_price);

Третий запрос — паттерн, который стоит запомнить: он возвращает строки только при наличии расхождения. Отсутствие возвращённых строк означает, что данные согласованы.

Использование SQL в автоматизации тестов

Подключение к базе данных из тестового кода позволяет напрямую проверять сохранение данных:

import psycopg2
import pytest

@pytest.fixture(scope="session")
def db():
    conn = psycopg2.connect(
        host="localhost",
        dbname="testdb",
        user="testuser",
        password="testpass"
    )
    yield conn
    conn.close()

def test_user_creation_persists(api_client, db):
    """Verify that creating a user via API persists to database."""
    api_client.post("/users", json={
        "name": "Alice",
        "email": "alice@test.com"
    })

    cursor = db.cursor()
    cursor.execute(
        "SELECT name, email, role FROM users WHERE email = %s",
        ("alice@test.com",)
    )
    row = cursor.fetchone()
    assert row is not None, "User was not persisted to database"
    assert row[0] == "Alice"
    assert row[2] == "viewer"  # Verify default role applied

Почему верификация через БД важна

Сценарий Ответ API Реальность в БД Без проверки БД
Баг кеширования Возвращает 201 Created Нет строки в таблице users Баг не обнаружен
Состояние гонки Возвращает успех Созданы дубликаты строк Повреждение данных не обнаружено
Баг значения по умолчанию Возвращает пользователя с role=null Столбец role равен NULL Отсутствующее значение по умолчанию не обнаружено
Баг мягкого удаления Возвращает 204 Deleted deleted_at всё ещё NULL Элемент выглядит удалённым, но таковым не является

Типичные паттерны запросов для QA

Проверка существования записи

-- Does this user exist?
SELECT COUNT(*) FROM users WHERE email = 'test@example.com';
-- Expected: 1

-- Does this order have line items?
SELECT COUNT(*) FROM line_items WHERE order_id = 'order-123';
-- Expected: > 0

Проверка значений по умолчанию

-- Check that defaults are applied on creation
SELECT role, active, email_verified, created_at, updated_at
FROM users
WHERE email = 'newuser@test.com';
-- Expected: role='viewer', active=true, email_verified=false,
-- created_at is recent, updated_at equals created_at

Проверка временных меток

-- Check that updated_at changes on modification
SELECT updated_at > created_at AS was_modified
FROM users
WHERE id = 123;
-- Expected: true (if user was updated)

-- Check that timestamps are reasonable (not in the future, not from 1970)
SELECT *
FROM orders
WHERE created_at > NOW()
   OR created_at < '2020-01-01';
-- Expected: no rows (no invalid timestamps)

Поиск аномалий данных

-- Find orphaned records (line items without orders)
SELECT li.id, li.order_id
FROM line_items li
LEFT JOIN orders o ON o.id = li.order_id
WHERE o.id IS NULL;

-- Find duplicate emails (data integrity issue)
SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

-- Find NULL values in required fields
SELECT id, name, email
FROM users
WHERE name IS NULL OR email IS NULL;

Параметризованные запросы (предотвращение SQL-инъекций)

Всегда используйте параметризованные запросы в тестовом коде:

# BAD: string interpolation — SQL injection vulnerability
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")

# GOOD: parameterized query — safe
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

# GOOD: named parameters
cursor.execute(
    "SELECT * FROM users WHERE email = %(email)s AND role = %(role)s",
    {"email": email, "role": "admin"}
)

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

Обработка результатов запросов в Python

# Using DictCursor for named access
from psycopg2.extras import DictCursor

def test_user_fields(db):
    cursor = db.cursor(cursor_factory=DictCursor)
    cursor.execute("SELECT * FROM users WHERE email = %s", ("alice@test.com",))
    user = cursor.fetchone()

    assert user["name"] == "Alice"
    assert user["role"] == "viewer"
    assert user["created_at"] is not None

# Fetching multiple rows
def test_all_active_users_have_email(db):
    cursor = db.cursor(cursor_factory=DictCursor)
    cursor.execute("SELECT id, email FROM users WHERE active = true")
    users = cursor.fetchall()

    for user in users:
        assert user["email"] is not None, f"User {user['id']} has no email"
        assert "@" in user["email"], f"User {user['id']} has invalid email: {user['email']}"

Практическое упражнение

Напишите SQL-запросы верификации для следующих сценариев:

  1. После создания пользователя через API проверьте, что пользователь существует в БД с корректными именем, email и ролью по умолчанию
  2. После создания заказа проверьте, что сумма заказа совпадает с суммой позиций
  3. После мягкого удаления пользователя проверьте, что временная метка deleted_at установлена и пользователь всё ещё существует в таблице
  4. Найдите всех пользователей, зарегистрированных за последние 24 часа (для пост-деплойной проверки)
  5. Найдите заказы с отрицательными итогами (не должны существовать)
  6. Интегрируйте один из этих запросов в pytest-тест с использованием psycopg2

Ключевые выводы

  • База данных — источник истины: проверяйте критическое сохранение данных с помощью SQL
  • Используйте SQL в автоматизации тестов для дополнения утверждений API
  • Типичные паттерны: проверка существования, верификация значений по умолчанию, валидация временных меток, обнаружение аномалий
  • Всегда используйте параметризованные запросы, даже в тестовом коде
  • DictCursor обеспечивает доступ к результатам запроса по имени для читаемых тестовых утверждений