Добавить в корзинуПозвонить
Найти в Дзене
Машинное обучение

🖥 Cовет по SQL-тестам: тестируйте не только результат запроса, а его инварианты

Обычно SQL проверяют так: SELECT * FROM orders WHERE status = 'paid'; И потом сравнивают: «вернулись нужные строки или нет». Но в реальных системах чаще ломается не сам happy path, а скрытые свойства данных. Например, для отчёта по заказам тест должен проверять не только конкретные строки, а правила: -- сумма по пользователям должна совпадать с общей суммой WITH by_user AS ( SELECT user_id, SUM(amount) AS total FROM orders WHERE status = 'paid' GROUP BY user_id ), overall AS ( SELECT SUM(amount) AS total FROM orders WHERE status = 'paid' ) SELECT (SELECT SUM(total) FROM by_user) = (SELECT total FROM overall) AS is_valid; То есть вы тестируете не «мне вернулось 10 строк», а: агрегаты не теряют деньги join не размножает строки фильтр не выкидывает валидные данные NULL не ломает расчёты сумма после группировки совпадает с суммой до группировки каждый order попадает ровно в одну категорию дедупликация не удаляет нужные записи Особенно полезный приём - тест на размножение строк посл

🖥 Cовет по SQL-тестам: тестируйте не только результат запроса, а его инварианты.

Обычно SQL проверяют так:

SELECT * FROM orders WHERE status = 'paid';

И потом сравнивают: «вернулись нужные строки или нет».

Но в реальных системах чаще ломается не сам happy path, а скрытые свойства данных.

Например, для отчёта по заказам тест должен проверять не только конкретные строки, а правила:

-- сумма по пользователям должна совпадать с общей суммой

WITH by_user AS (

SELECT user_id, SUM(amount) AS total

FROM orders

WHERE status = 'paid'

GROUP BY user_id

),

overall AS (

SELECT SUM(amount) AS total

FROM orders

WHERE status = 'paid'

)

SELECT

(SELECT SUM(total) FROM by_user) = (SELECT total FROM overall) AS is_valid;

То есть вы тестируете не «мне вернулось 10 строк», а:

агрегаты не теряют деньги

join не размножает строки

фильтр не выкидывает валидные данные

NULL не ломает расчёты

сумма после группировки совпадает с суммой до группировки

каждый order попадает ровно в одну категорию

дедупликация не удаляет нужные записи

Особенно полезный приём - тест на размножение строк после JOIN:

WITH before_join AS (

SELECT COUNT(*) AS cnt

FROM orders

),

after_join AS (

SELECT COUNT(*) AS cnt

FROM orders o

JOIN users u ON u.id = o.user_id

)

SELECT

after_join.cnt <= before_join.cnt AS no_unexpected_multiplication

FROM before_join, after_join;

Если после JOIN строк стало больше без явной причины - у вас почти наверняка проблема с кардинальностью.

Хороший SQL-тест проверяет не только ответ, а свойства запроса, которые должны оставаться истинными при любых данных. Именно так ловятся баги, которые не видно на маленьком тестовом датасете.