Базы данных — это фундамент большинства приложений, хранящие критические бизнес-данные и обеспечивающие основной функционал. Однако тестирование баз данных часто получает меньше внимания, чем тестирование приложений, что приводит к проблемам в продакшене, от повреждения данных до катастрофической деградации производительности. Это всеобъемлющее руководство охватывает ключевые аспекты тестирования баз данных—от обеспечения целостности данных до валидации сложных миграций—снабжая QA-инженеров практическими стратегиями для тщательной валидации баз данных.
Понимание Database Testing
Database testing валидирует слой данных приложений, фокусируясь на:
- Целостность и консистентность данных
- Корректность схемы
- Поведение транзакций (ACID-свойства)
- Производительность под нагрузкой
- Безопасность и контроль доступа
- Механизмы backup и восстановления
- Процессы миграции и обновления
Тестирование целостности и консистентности данных
Целостность данных обеспечивает, что данные остаются точными, полными и консистентными на протяжении их жизненного цикла.
Типы целостности данных
1. Entity Integrity (Целостность сущности)
Entity integrity требует, чтобы каждая таблица имела первичный ключ и чтобы первичный ключ был уникальным и не null.
-- Тест 1: Проверить, что primary key существует
SELECT
table_name,
COUNT(*) as pk_count
FROM information_schema.table_constraints
WHERE constraint_type = 'PRIMARY KEY'
AND table_schema = 'your_database'
GROUP BY table_name
HAVING pk_count = 0;
-- Ожидается: Нет результатов (каждая таблица должна иметь PK)
-- Тест 2: Проверить отсутствие NULL первичных ключей
SELECT * FROM users WHERE id IS NULL;
-- Ожидается: 0 строк
-- Тест 3: Проверить отсутствие дублированных первичных ключей
SELECT id, COUNT(*) as count
FROM users
GROUP BY id
HAVING count > 1;
-- Ожидается: 0 строк
2. Referential Integrity (Ссылочная целостность)
Referential integrity обеспечивает, что отношения между таблицами остаются консистентными.
-- Тест: Найти orphaned записи заказов
SELECT o.*
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL;
-- Ожидается: 0 строк (все заказы должны иметь валидных пользователей)
-- Тестирование CASCADE-поведения
BEGIN;
INSERT INTO users (id, email) VALUES (999, 'test@example.com');
INSERT INTO orders (id, user_id, total) VALUES (9999, 999, 100.00);
-- Тест CASCADE DELETE
DELETE FROM users WHERE id = 999;
-- Проверить, что заказ также был удалён
SELECT * FROM orders WHERE id = 9999;
-- Ожидается: 0 строк
ROLLBACK;
3. Domain Integrity (Доменная целостность)
Domain integrity обеспечивает, что значения колонок придерживаются определённых constraints.
-- Тест: Проверить NOT NULL constraints
BEGIN;
INSERT INTO users (id, email, created_at)
VALUES (1, NULL, NOW());
-- Ожидается: Ошибка (если email является NOT NULL)
ROLLBACK;
-- Тест: Проверить CHECK constraints
BEGIN;
INSERT INTO products (id, name, price)
VALUES (1, 'Test Product', -10.00);
-- Ожидается: Ошибка (если существует CHECK constraint: price >= 0)
ROLLBACK;
-- Найти записи с невалидными паттернами данных
SELECT * FROM users WHERE email NOT LIKE '%@%';
SELECT * FROM orders WHERE total < 0;
4. User-Defined Integrity (Пользовательская целостность)
User-defined integrity применяет бизнес-правила, специфичные для приложения.
-- Тест: Итоги заказов соответствуют сумме строк заказа
SELECT
o.id AS order_id,
o.total AS order_total,
SUM(oi.quantity * oi.unit_price) AS calculated_total,
ABS(o.total - SUM(oi.quantity * oi.unit_price)) AS difference
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, o.total
HAVING ABS(o.total - SUM(oi.quantity * oi.unit_price)) > 0.01;
-- Ожидается: 0 строк (в пределах толерантности округления)
-- Тест: Балансы счетов корректны
SELECT
a.id,
a.balance AS recorded_balance,
COALESCE(SUM(t.amount), 0) AS calculated_balance,
a.balance - COALESCE(SUM(t.amount), 0) AS difference
FROM accounts a
LEFT JOIN transactions t ON a.id = t.account_id
GROUP BY a.id, a.balance
HAVING ABS(a.balance - COALESCE(SUM(t.amount), 0)) > 0.01;
-- Ожидается: 0 строк
Тестирование консистентности в распределенных системах
Тестирование eventual consistency
import time
def test_eventual_consistency(db_writer, db_reader):
# Записать в master
user_id = db_writer.create_user(email="test@example.com")
# Retry с exponential backoff
for attempt in range(5):
user = db_reader.get_user(user_id)
if user is None:
time.sleep(2 ** attempt)
else:
break
# После retry должно быть консистентно
assert user is not None
assert user.email == "test@example.com"
Performance Tuning и тестирование
Идентификация медленных запросов
PostgreSQL:
-- Просмотр медленных запросов из pg_stat_statements
SELECT
query,
calls,
total_exec_time,
mean_exec_time,
max_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
-- Идентифицировать запросы с высокой вариацией
SELECT
query,
calls,
mean_exec_time,
stddev_exec_time,
(stddev_exec_time / mean_exec_time) AS coefficient_of_variation
FROM pg_stat_statements
WHERE calls > 100
ORDER BY (stddev_exec_time / mean_exec_time) DESC;
MySQL:
-- Включить slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- Просмотр сводки медленных запросов
SELECT
query,
exec_count,
avg_latency,
max_latency
FROM sys.statement_analysis
ORDER BY avg_latency DESC
LIMIT 20;
Оптимизация индексов
Идентификация отсутствующих индексов:
-- PostgreSQL: Найти sequential scans на больших таблицах
SELECT
schemaname,
tablename,
seq_scan,
seq_tup_read,
idx_scan
FROM pg_stat_user_tables
WHERE seq_scan > 0
AND seq_tup_read / seq_scan > 10000
ORDER BY seq_tup_read DESC;
Тестирование эффективности индекса:
-- Тест EXPLAIN plan до добавления индекса
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'user@example.com';
-- Добавить индекс
CREATE INDEX idx_users_email ON users(email);
-- Тест EXPLAIN plan после добавления индекса
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'user@example.com';
Техники оптимизации запросов
**Избегать SELECT ***
-- Плохо: Получает все колонки
SELECT * FROM users WHERE id = 123;
-- Хорошо: Получать только необходимые колонки
SELECT id, email, name FROM users WHERE id = 123;
Избегать N+1 запросов
# Плохо: N+1 query problem
users = db.query("SELECT * FROM users")
for user in users:
orders = db.query(f"SELECT * FROM orders WHERE user_id = {user.id}")
# Хорошо: Использовать JOIN
result = db.query("""
SELECT u.id, u.name, o.id as order_id, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
""")
Migration Testing
Миграции баз данных — одни из самых рискованных операций в разработке ПО.
Стратегия тестирования миграций
1. Тестировать в non-production сначала (всегда!) 2. Использовать инструменты миграции (Flyway, Liquibase, Alembic) 3. Делать миграции обратимыми 4. Тестировать с production-like объёмами данных 5. Измерять длительность миграции
Тестирование schema-миграций
-- Миграция: добавить колонку
-- UP
ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT FALSE NOT NULL;
CREATE INDEX idx_users_email_verified ON users(email_verified);
-- DOWN (rollback)
DROP INDEX idx_users_email_verified;
ALTER TABLE users DROP COLUMN email_verified;
Чеклист тестирования:
- ✓ Миграция выполняется без ошибок
- ✓ Дефолтное значение корректно применено
- ✓ NOT NULL constraint не ломает существующие данные
- ✓ Индекс создан успешно
- ✓ Rollback-скрипт работает корректно
Тестирование миграций данных
-- Миграция: Разделить колонку name на first_name и last_name
ALTER TABLE users ADD COLUMN first_name VARCHAR(100);
ALTER TABLE users ADD COLUMN last_name VARCHAR(100);
UPDATE users
SET
first_name = SPLIT_PART(name, ' ', 1),
last_name = SPLIT_PART(name, ' ', 2)
WHERE name IS NOT NULL;
Тестирование масштабных миграций
Batch-обработка:
-- Вместо единичного UPDATE на миллионы строк:
DO $$
DECLARE
batch_size INT := 10000;
rows_updated INT;
BEGIN
LOOP
UPDATE users
SET status = 'active'
WHERE id IN (
SELECT id FROM users
WHERE status IS NULL
LIMIT batch_size
);
GET DIAGNOSTICS rows_updated = ROW_COUNT;
EXIT WHEN rows_updated = 0;
COMMIT;
RAISE NOTICE 'Updated % rows', rows_updated;
PERFORM pg_sleep(0.1);
END LOOP;
END $$;
Тестирование NoSQL баз данных
Тестирование MongoDB
// Определить правила валидации схемы
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["email", "createdAt"],
properties: {
email: {
bsonType: "string",
pattern: "^.+@.+$"
},
age: {
bsonType: "int",
minimum: 0,
maximum: 150
}
}
}
}
});
// Тест валидации
db.users.insertOne({
email: "user@example.com",
age: 25,
createdAt: new Date()
});
Тестирование Cassandra
from cassandra.cluster import Cluster
from cassandra import ConsistencyLevel
def test_consistency_levels():
# Писать с QUORUM
insert_stmt = session.prepare("""
INSERT INTO users (id, email, name)
VALUES (?, ?, ?)
""")
insert_stmt.consistency_level = ConsistencyLevel.QUORUM
session.execute(insert_stmt, (uuid.uuid4(), 'test@example.com', 'Test'))
Тестирование Redis
import redis
import time
def test_cache_expiration():
r = redis.Redis(host='localhost', port=6379)
# Установить значение с TTL
r.setex('user:123:profile', 10, '{"name": "John"}')
# Немедленно доступно
assert r.get('user:123:profile') is not None
# Истекло после 11 секунд
time.sleep(11)
assert r.get('user:123:profile') is None
Заключение
Тестирование баз данных — это критическая дисциплина, требующая как технической глубины, так и систематической строгости.
Ключевые выводы:
- Целостность данных первостепенна - Использовать constraints, валидировать relationships
- Производительность масштабируется экспоненциально - Оптимизировать рано, мониторить непрерывно
- Миграции высокорискованны - Тестировать тщательно, делать обратимыми
- NoSQL (как обсуждается в SQL Injection and XSS: Finding Vulnerabilities) требует других стратегий - Понимать consistency models
- Автоматизировать database-тесты (как обсуждается в Database Performance Testing: Query Optimization) - Интеграция в CI/CD ловит регрессии рано
- Мониторить production databases (как обсуждается в Stress Testing vs Volume Testing: Key Differences) - Использовать slow query logs, отслеживать метрики
Инвестируйте в надежные практики тестирования баз данных, и вы предотвратите повреждение данных, избежите кризисов производительности и доставите надежные системы, которым пользователи могут доверять.