Modern QA2026Маскирование и анонимизация данных
Join

Course15 SQL & Database Testing

Foundations · Chapter 15

Маскирование и анонимизация данных

Updated Jul 2026

Тестовые окружения нуждаются в реалистичных данных без раскрытия реальной информации клиентов. Маскирование данных преобразует продакшен-данные в безопасные тестовые данные, сохраняя при этом формат, связи и статистические свойства. Требования соответствия регуляциям (GDPR, HIPAA, PCI DSS) делают это не просто лучшей практикой, а юридическим требованием.

Почему маскирование данных важно

Без маскирования С маскированием
Тестовые окружения содержат реальные данные клиентов Тестовые данные выглядят реалистично, но являются поддельными
Утечка данных в staging раскрывает реальных клиентов Утечка в staging не раскрывает ничего конфиденциального
Нарушения соответствия (GDPR, HIPAA) Соответствие по дизайну
Нельзя делиться тестовыми окружениями с подрядчиками Безопасно для совместного использования с кем угодно
Копия продакшен-данных требует согласований безопасности Маскированная копия безопасна для распространения

Техники маскирования

Тип данных Оригинал Маскированный Техника
Email john@company.com user_8a3f@masked.test Замена на основе хеша
Телефон +1-555-867-5309 +1-555-000-0042 Рандомизация с сохранением формата
SSN 123-45-6789 XXX-XX-6789 Частичная редакция
Кредитная карта 4111111111111111 4111XXXXXXXX1111 Сохранены первые/последние четыре
Имя John Smith David Wilson Подстановка по справочнику
Адрес 123 Main St, NY 456 Oak Ave, CA Рандомизированная замена
Дата рождения 1985-03-15 1985-07-22 Перемешаны день/месяц, год сохранён
Зарплата $85,000 $82,000 Возмущение с сохранением диапазона

Требования к маскированию

1. Сохранение формата

Маскированные данные должны выглядеть как исходный тип данных. Email должны выглядеть как email. Номера телефонов должны быть в валидном формате.

def test_masked_emails_have_valid_format(masked_db):
    cursor = masked_db.cursor()
    cursor.execute("SELECT email FROM users LIMIT 100")
    for row in cursor.fetchall():
        email = row[0]
        assert "@" in email, f"Invalid email format: {email}"
        assert "." in email.split("@")[1], f"Invalid domain: {email}"

2. Сохранение связей

Один и тот же клиент должен иметь одинаковое маскированное имя во всех таблицах. Если "john@company.com" стал "user_8a3f@masked.test" в таблице users, он должен быть таким же в таблице orders и в журнале аудита.

def test_masked_relationships_consistent(masked_db):
    cursor = masked_db.cursor()
    cursor.execute("""
        SELECT u.email AS user_email, o.customer_email AS order_email
        FROM users u
        JOIN orders o ON o.user_id = u.id
        LIMIT 50
    """)
    for row in cursor.fetchall():
        assert row[0] == row[1], \
            f"Email mismatch: users.email={row[0]}, orders.customer_email={row[1]}"

3. Ссылочная целостность

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

def test_masked_data_referential_integrity(masked_db):
    cursor = masked_db.cursor()

    # No orphaned orders (all orders reference existing users)
    cursor.execute("""
        SELECT COUNT(*)
        FROM orders o
        LEFT JOIN users u ON u.id = o.user_id
        WHERE u.id IS NULL
    """)
    orphans = cursor.fetchone()[0]
    assert orphans == 0, f"Found {orphans} orders referencing non-existent users"

def test_masked_foreign_keys_valid(masked_db):
    cursor = masked_db.cursor()
    cursor.execute("""
        SELECT COUNT(DISTINCT o.user_id) AS referenced,
               (SELECT COUNT(*) FROM users) AS total_users
        FROM orders o
    """)
    row = cursor.fetchone()
    # All referenced user IDs should exist in the users table
    assert row[0] <= row[1]

4. Необратимость

Маскирование должно быть односторонним. Не должно быть возможности обратить маскирование для восстановления исходных данных.

def test_masking_is_irreversible(original_db, masked_db):
    """No original values should appear in masked data."""
    # Get a sample of original emails
    orig_cursor = original_db.cursor()
    orig_cursor.execute("SELECT email FROM users LIMIT 100")
    original_emails = {row[0] for row in orig_cursor.fetchall()}

    # Check none of them appear in masked data
    masked_cursor = masked_db.cursor()
    masked_cursor.execute("SELECT email FROM users")
    masked_emails = {row[0] for row in masked_cursor.fetchall()}

    overlap = original_emails & masked_emails
    assert len(overlap) == 0, f"Original emails found in masked data: {overlap}"

Сохранение статистических свойств

Для тестирования аналитики и отчётов маскированные данные должны сохранять статистические свойства:

def test_masked_data_preserves_statistics(original_db, masked_db):
    """Distribution of key metrics should be similar after masking."""
    # Compare order count distribution
    for db, label in [(original_db, "original"), (masked_db, "masked")]:
        cursor = db.cursor()
        cursor.execute("SELECT COUNT(*) FROM orders")
        count = cursor.fetchone()[0]
        cursor.execute("SELECT AVG(total) FROM orders")
        avg_total = cursor.fetchone()[0]
        print(f"{label}: {count} orders, avg total ${avg_total:.2f}")

    # Exact numbers will differ, but order of magnitude should be the same

Требования соответствия

Регуляция Влияние на тестирование
GDPR Тестовые окружения должны использовать маскированные данные; проверьте, что удаление убирает все ссылки
HIPAA Никаких реальных данных пациентов в тестовых окружениях, никогда
PCI DSS Тестируйте с валидным по формату, но поддельным номером карты (4111...)
SOC 2 Доступ к продакшен-данным должен аудироваться; маскированные данные сокращают область аудита
CCPA Аналогично GDPR — данные жителей Калифорнии должны быть защищены

GDPR-специфичные тесты

def test_gdpr_deletion_removes_all_references(api, db):
    """GDPR right to erasure: deleting a user removes ALL their data."""
    # Create a user with orders and activity
    user = create_user_with_full_history(api)

    # Request deletion (GDPR right to erasure)
    api.delete(f"/users/{user['id']}/gdpr-delete")

    # Verify complete removal
    cursor = db.cursor()

    cursor.execute("SELECT * FROM users WHERE id = %s", (user["id"],))
    assert cursor.fetchone() is None, "User record still exists"

    cursor.execute("SELECT * FROM orders WHERE user_id = %s", (user["id"],))
    assert cursor.fetchone() is None, "User orders still exist"

    cursor.execute("SELECT * FROM audit_log WHERE user_id = %s", (user["id"],))
    assert cursor.fetchone() is None, "User audit trail still exists"

    cursor.execute("SELECT * FROM session_logs WHERE user_id = %s", (user["id"],))
    assert cursor.fetchone() is None, "User session logs still exist"

Инструменты маскирования

Инструмент Тип Лучше всего для
pg_anonymize Расширение PostgreSQL Простое маскирование на основе правил
Delphix Корпоративная платформа Масштабное автоматизированное маскирование
DataMasker Коммерческий инструмент SQL Server и Oracle
Faker (Python/JS) Библиотека Генерация поддельных данных для тестовых окружений
Пользовательские скрипты DIY Полный контроль над логикой маскирования

Простое маскирование с Faker

from faker import Faker

fake = Faker()

def mask_users_table(source_conn, target_conn):
    source = source_conn.cursor()
    target = target_conn.cursor()

    source.execute("SELECT id, name, email, phone FROM users")
    for row in source.fetchall():
        target.execute(
            "INSERT INTO users (id, name, email, phone) VALUES (%s, %s, %s, %s)",
            (row[0], fake.name(), fake.email(), fake.phone_number())
        )
    target_conn.commit()

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

  1. Напишите скрипт маскирования, который преобразует email и номера телефонов с сохранением формата
  2. Напишите тесты, проверяющие маскированные данные: сохранение формата, согласованность связей, ссылочная целостность
  3. Напишите GDPR-тест удаления: создайте пользователя с активностью в 4 таблицах, удалите его, проверьте, что все следы удалены
  4. Протестируйте, что никакие оригинальные значения не появляются в маскированном наборе данных
  5. Сравните статистику (количество, среднее, распределение) между оригинальным и маскированным наборами данных

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

  • Маскирование данных — юридическое требование (GDPR, HIPAA, PCI DSS), а не просто лучшая практика
  • Маскированные данные должны сохранять формат, связи и ссылочную целостность
  • Маскирование должно быть необратимым — невозможно восстановить оригинальные данные
  • Тестируйте качество маскирования: формат, согласованность, ссылочная целостность, необратимость
  • Право на удаление GDPR требует тестирования, что удаление убирает ВСЕ ссылки во ВСЕХ таблицах

Тезис для собеседования: «Я использую SQL как уровень верификации в автоматизации тестов — после API-вызова, создающего запись, я запрашиваю базу данных для подтверждения, что данные сохранены с правильными значениями по умолчанию и ограничениями. Я уверенно работаю с JOIN, GROUP BY, подзапросами и управлением тестовыми данными на основе транзакций. Я также тестирую миграции базы данных отдельно: применяю, проверяю целостность данных, откатываю и проверяю снова. Для NoSQL хранилищ, таких как Redis и MongoDB, я тестирую поведение TTL, структуру документов и инвалидацию кеша.»