Мокінг бази даних може бути дуже спокусливим, коли ви намагаєтесь рухатись швидко. Адже так, підтримка реального тестового середовища для бази даних може бути дійсно болючою, але покладатись на моки — це ще гірше.
Що таке мок бази даних?
Мок бази даних — це фейкова імплементація бази даних, з якою ваш код взаємодіє під час тестів. Замість того, щоб підключатись до реальної бази даних, ваш тест використовує об'єкт або модуль, що імітує те, як база даних буде реагувати. Ви можете точно вказати, які дані "повертаються" з запитів або як "викидаються" помилки, при цьому не виконуючи жодного SQL чи не дотримуючись реальних обмежень схеми.
Чому покладатись на моки для тестування не є хорошою ідеєю
Це може звучати добре в теорії, але пропуск тестування на реальній базі даних може призвести до різноманітних проблем. Неможливо створити мок, який би обробляв усі граничні випадки (і, давайте будемо чесними, знайти ці граничні випадки під час звичайного тесту і так досить складно).
Ви пропустите критичні проблеми
Виконання схеми (унікальні обмеження, зовнішні ключі, значення за замовчуванням) не буде відображено в моках, тому реальні порушення (наприклад, дублікати email або відсутні поля) залишаться непоміченими до моменту потрапляння в продакшн. Те саме стосується специфічних для бази даних правил.
Немає видимості у продуктивності
Використання індексів, плани запитів і поведінка блокувань невидимі при використанні моків. Проблеми з паралелізмом у реальному світі (наприклад, взаємні блокування) виявляються лише при тестуванні з реальною базою даних.
Підтримка актуальності моків — важка задача
Будь-які зміни потребують оновлення вашої мок-налаштування. Якщо ви забудете оновити мок для нового обмеження або поведінки, ви отримаєте хибнопозитивні результати в тестах і виявите невідповідність тільки після потрапляння в продакшн.
Ви насправді не тестуєте вашу бізнес-логіку
Можливо, у вас є бізнес-правила, що знаходяться в самій базі даних. Якщо тести ніколи не торкаються реальної БД, вони ніколи не перевіряють, чи працюють ці правила коректно або чи не змінюються вони в процесі змін коду.
Контейнери — альтернатива, але вони потребують більше зусиль, ніж ви думаєте
Інструменти на зразок Docker Compose полегшують створення бази даних Postgres, тому вони є популярною альтернативою мокам.
Проблема в тому, що коли ви налаштовуєте нову базу даних, ви починаєте з порожньої схеми. Але звісно, вам потрібно заповнити її даними, щоб тестувати додаток змістовно. Також потрібно оновлювати схеми з часом (та сама проблема, що й з моками).
Для вирішення цього ви могли б
- реалізувати покрокові скрипти або файли, що визначають, як створювати або змінювати вашу схему
- коли тестовий контейнер запускається, ваш тестовий код або тестовий процес запускає ці міграції для побудови точної схеми, яка відповідає продакшн (або вашому поточному середовищу розробки)
- для заповнення бази даних даними можна застосовувати різні стратегії (див. нижче)
Заповнення бази даних Postgres тестовими даними
Seed дані
Seed дані — це невеликі набори тестових записів, які ви завантажуєте в базу даних, що представляють такі речі, як стандартні облікові записи користувачів, довідкові дані (наприклад, список країн, стандартні ролі) тощо.
Ви могли б створити скрипт (наприклад, seeds.sql), який вставляє необхідні дані; кожного разу, коли контейнер запускається, ви запускаєте цей скрипт, і база даних готова з достатньою кількістю "реалістичних" даних для проходження ваших тестів.
Fixtures (Фіксури)
Фіксури — це ще один варіант. Це попередньо визначені набори даних (файли YAML, JSON або SQL), які точно вказують, які записи потрібно вставити. Вони стають більш об'ємними, ніж seed дані. Ви можете завантажувати фіксури у ваші тести для налаштування відомого стану (наприклад, створити таблицю замовлень з деякими рядками).
Трансформовані дані з продакшн
Ви могли б використовувати невелику підмножину або знімок реальних продакшн даних, які ви анонімізуєте або обрізаєте для безпеки/конфіденційності, а потім завантажуєте їх у тестову базу даних.
Це має сенс. Найкращий спосіб відтворити реальні сценарії — використовувати реальні дані, щоб виявити складні взаємозв'язки або граничні випадки, які важко підробити.
Основна проблема полягає в тому, що правильно трансформувати продакшн дані не так легко.
Вам потрібно ретельно видаляти або змішувати чутливу інформацію (email-адреси, паролі, особисті дані). Знімок також має залишатись малим, інакше ваші тести будуть уповільнюватись.
Новий метод: гілки Neon
Альтернативою Docker для Postgres є використання Postgres через Neon для тестування. Neon — це безсерверна платформа для Postgres (з безкоштовним планом), що дозволяє швидко створювати та видаляти гілки бази даних для тестових чи розробницьких цілей.
У моделі Neon ви налаштовуєте одну основну базу даних Postgres як джерело істини для всіх середовищ, а з неї створюєте стільки баз даних, скільки хочете, як дочірні гілки. Велика перевага в тому, що кожне середовище містить точну копію схеми та даних, і це так само швидко, як Docker.
На відміну від цього, підхід з Docker вимагає від вас обробляти автоматизаційні скрипти для запуску міграцій / завантаження seed даних. Якщо ви хочете скинути тестову базу до відомого стану, вам доведеться перебудувати контейнер чи налаштування Compose, або видалити та відновити базу даних в контейнері, а потім знову запустити seed або міграції. В Neon це займе один API виклик, оскільки будь-яку дочірню гілку можна синхронізувати з її батьківською в будь-який час.
Створення тестової бази даних з Neon
Створити тестове середовище дуже просто з гілками Neon, коли ви вже маєте "відомо хорошу" схему або частковий набір даних, з якого можна зробити гілку:
- Можна зробити гілку прямо з продакшн або staging: Якщо у вас немає PII або іншої чутливої інформації, ви можете створити нову гілку з вашої продакшн чи staging бази даних. Тестова гілка автоматично успадковує існуючу схему та дані.
- Можна зробити гілку з шаблону: Або ви можете створити гілку з уже мігрованої бази даних "шаблону" — це спосіб, якщо вам потрібно тестувати на трансформованих або специфічних даних.
Скидання середовищ в один клік
Цей метод також дозволяє повністю скинути тестове середовище (видаливши всі зміни та повернувшись до відомого стану батьківської гілки) в один клік або за допомогою API виклику, використовуючи функцію Branch Restore від Neon.
Що якщо я використовую SQLite?
Розглянемо ще одну опцію. Якщо ви використовуєте Postgres, може бути спокусливо тестувати на SQLite — ви можете створити нову базу даних за мілісекунди, без окремого сервера БД або контейнера для управління.
На відміну від мока, SQLite — це реальний механізм бази даних, тому він забезпечує деяку цілісність (зовнішні ключі, якщо увімкнено, базові унікальні обмеження тощо), і може виявити певні помилки, пов'язані з даними, які чистий мок може пропустити.
Але будь-яка тестова стратегія, що виключає реальну продакшн базу даних, є ризикованою. Ви можете розглянути можливість використання SQLite для тестів на рівні юнітів, якщо ви дійсно хочете його використовувати, але інтеграційні/акцептаційні тести повинні виконуватись проти реального середовища Postgres, щоб виявити неминучі невідповідності.
Щоб підкреслити це, ось деякі проблеми, які можуть виникнути, якщо ви покладаєтесь на SQLite для всіх своїх тестів:
- SQLite не реалізує всі специфічні для Postgres SQL функції або типи даних (наприклад, jsonb, певні віконні функції, часткові індекси, кастомні оператори). Якщо ваша логіка або запити залежать від цих функцій, вони не проваляться в SQLite, навіть якщо використовуються неправильно.
- SQLite більш поблажливий щодо типів стовпців та обмежень за замовчуванням. Наприклад, ви можете вставити рядок у стовпець типу ціле число, якщо не будуть дотримані правила. Postgres є більш строгим, тому код, що "працює" в SQLite, може зламатися в продакшн через невідповідність типів.
- SQLite має простішу модель паралелізму, ніж Postgres, і блокує весь файл бази даних для запису. Postgres підтримує блокування на рівні рядка, кілька одночасних підключень та більш складні рівні ізоляції.
- Деякі DDL операції Postgres можуть не мати прямого еквіваленту в SQLite. Індексація, тригери, збережені процедури та певні обмеження зовнішніх ключів можуть працювати по-різному.
Якщо ви зацікавлені спробувати Neon, ви можете зареєструватися на безкоштовний план тут.
Перекладено з: Beyond Database Mocks: How to Spin Up Real Postgres Testing Environments in Seconds