Бази даних у роботі QA
Зміст
UI показує «120 активних замовлень», API у відповіді віддає 118, а користувач у чаті клянеться, що бачить 121. Хто з них має рацію — знає лише одне місце: база даних. Усе, що застосунок показує на екрані чи повертає через API, — це вже інтерпретація: щось відфільтрувалося, щось закешувалося, щось порахувалося на льоту. А в базі лежать самі факти, без прикрас. Тому рано чи пізно тестувальник перестає вірити екрану на слово й іде дивитися в першоджерело — і для цього потрібен SQL.
Ця глава — вступна карта всього розділу. Тут ми не пишемо запитів (це почнеться з наступних глав), а розбираємося з рамкою: що таке база даних і чим вона відрізняється від СУБД, де вона стоїть у клієнт-серверній архітектурі, навіщо QA взагалі туди лізе, чим реляційні бази відрізняються від нереляційних, як під'єднатися клієнтом і — найважливіше для новачка — яке одне правило рятує від катастрофи на проді. Решта розділу нарощує SQL-навички; ця глава дає орієнтацію, щоб було зрозуміло, куди ці навички прикладати.
База даних, СУБД і де вони в архітектурі
Спершу розведемо два поняття, які початківці плутають. База даних (database, БД) — це власне організований набір даних: таблиці з замовленнями, користувачами, платежами. Система керування базами даних (database management system, СУБД, DBMS) — це програма, яка ці дані зберігає, віддає й захищає: PostgreSQL, MySQL, Microsoft SQL Server, Oracle. Коли кажуть «у нас Postgres», мають на увазі СУБД; а конкретні дані застосунку — це база (часто й не одна) всередині неї. Різниця не педантична: підключаєшся ти до СУБД, а працюєш — з конкретною базою в ній.
Тепер про місце в системі. У клієнт-серверній архітектурі (див. главу Клієнт-сервер і як працює веб) сам застосунок даних не зберігає. Коли ти натискаєш кнопку, сервер застосунку виконує бізнес-логіку, а по дані звертається до окремого процесу — СУБД. Спілкуються вони мовою SQL по мережі: застосунок надсилає запит, СУБД повертає рядки. Дуже часто СУБД працює навіть на окремій машині зі своїм мережевим портом — це окремий сервер зі своєю відповідальністю.
Ключове для QA: до тієї самої СУБД можна під'єднатися напряму своїм клієнтом — в обхід UI й API, одразу до джерела істини. Там видно все як є: без кешу, без форматування, без прав доступу, які ховають частину даних у застосунку.
Одне застереження на майбутнє: «база» рідко буває одна. Часто є основна база (primary) і її копії для читання (репліки), між якими дані доїжджають із затримкою; у мікросервісній архітектурі кожен сервіс може мати власну базу. Через це той самий факт іноді видно в одному місці й ще не видно в іншому — до цього ми повернемося, коли говоритимемо про консистентність.
Навіщо тестувальнику SQL
SQL для QA — це не про те, щоб писати запити для застосунку (це робота розробника). Це про те, щоб читати й перевіряти. Ось живі сценарії, у яких без бази не обійтися:
- Звірити правду з інтерфейсом. Дашборд показує суму, звіт — кількість, лист — статус. SQL дає незалежний оракул: порахувати те саме напряму в базі й порівняти. Розбіжність цифри на UI й у базі — класичний баг.
- Підготувати тестові дані й потрібний стан. Щоб перевірити «що буде із замовленням, якому 400 днів», інколи простіше знайти чи виставити такий запис у базі, ніж клікати сценарій годинами. (Стратегія тестових даних — окрема тема, глава Тестові дані і БД в автотестах.)
- Розслідувати баг. Користувач зареєструвався, а листа не отримав — запис у таблиці
usersє, а вemail_queueнемає? Значить, проблема не в реєстрації, а в черзі листів. База показує, на якому кроці ланцюжок обірвався. - Бачити невидиме. Застосунок часто ховає дані: «видалений» запис насправді лишається з міткою
deleted_at, фонове завдання має статус, аудит-лог пише історію змін. Через UI цього не побачиш, а в базі — прямо. - Підсилити баг-репорт. «Після оплати замовлення лишається в статусі
new» звучить переконливіше з рядком із бази, ніж зі скриншотом.
Від мануального тестувальника сьогодні часто очікують базового SQL, а на співбесідах рівня trainee/junior це — стандартний блок питань. Хороша новина: щоб бути корисним, не треба знати СУБД як адміністратор — достатньо впевнено вибирати й фільтрувати дані.
Реляційні та нереляційні бази — оглядово
Реляційні бази зберігають дані в таблицях: рядки (записи) і стовпці (поля), із заздалегідь заданою схемою (schema) — які є колонки й якого типу. Таблиці пов'язані між собою (замовлення посилається на користувача), працюють через SQL і дають сильні гарантії цілісності — про них ітиметься в главі Транзакції та ACID. Це PostgreSQL, MySQL/MariaDB, SQL Server, Oracle. Саме на них тримається більшість бізнес-систем, тому 17 із 18 глав цього розділу — про реляційні бази й SQL.
Нереляційні (NoSQL) — це парасолька над усім, що влаштоване інакше. Кілька родин:
| Тип NoSQL | Ідея | Приклад |
|---|---|---|
| Документні | Записи-документи (JSON-подібні), гнучка схема | MongoDB |
| Ключ-значення | Проста пара «ключ → значення», дуже швидко | Redis |
| Колонкові | Оптимізовані під величезні обсяги й запис | Cassandra |
| Графові | Вузли й зв'язки між ними | Neo4j |
Головні відмінності від реляційних — гнучка або відсутня схема (можна класти документи різної форми в одну колекцію) і легше горизонтальне масштабування, але за це часто платять слабшими гарантіями узгодженості (eventual consistency — «колись узгодиться»). Для QA це означає інший клас багів: не «порушено зовнішній ключ», а «поле, яке ми чекали, у половині документів просто відсутнє». Оглядово NoSQL розбирає остання глава розділу — NoSQL для QA: MongoDB, Redis і консистентність.
| Аспект | Реляційна БД | NoSQL |
|---|---|---|
| Модель даних | Таблиці, рядки, стовпці | Документи / ключ-значення / граф |
| Схема | Фіксована, задана наперед | Гнучка або відсутня |
| Мова | SQL (з діалектами) | У кожної своя (API, власна мова) |
| Зв'язки | Через ключі, на рівні БД | Часто на рівні застосунку |
| Типова сила | Цілісність, складні запити | Масштаб, швидкість, гнучкість |
Важливо не сприймати це як «застаріле проти сучасного»: це різні інструменти під різні задачі. Багато систем використовують і те, і те одночасно — реляційну базу як основне сховище й Redis як кеш поверх неї.
Клієнти й параметри підключення
Щоб зазирнути в базу, потрібен клієнт — програма, яка вміє під'єднатися до СУБД і виконати запит. Їх два типи. Консольні (CLI): psql для PostgreSQL, mysql для MySQL — швидко, легко в скриптах, але без візуалізації. Графічні (GUI): DBeaver (безкоштовний, універсальний — розуміє десятки СУБД), DataGrip (від JetBrains, платний, потужний), pgAdmin (спеціалізований під PostgreSQL). Для новачка GUI зручніший: дерево таблиць, автодоповнення, результат у вигляді сітки.
Незалежно від клієнта, щоб під'єднатися, потрібен той самий набір параметрів:
| Параметр | Що це | Приклад |
|---|---|---|
| host | Адреса сервера СУБД | db.stage.example.com |
| port | Мережевий порт | 5432 (PostgreSQL) |
| database | Ім'я бази всередині СУБД | shop |
| user | Обліковий запис | qa_readonly |
| password | Пароль | (з менеджера секретів) |
| ssl / sslmode | Чи шифрувати з'єднання | require |
Порти за замовчуванням варто впізнавати: PostgreSQL — 5432, MySQL/MariaDB — 3306, SQL Server — 1433, Oracle — 1521. Це дефолти; на конкретному стенді порт може бути іншим.
Той самий набір часто пакують в один URL-подібний рядок підключення (connection string):
postgresql://qa_readonly:password@db.stage.example.com:5432/shop
а в консолі це виглядає так:
psql -h db.stage.example.com -p 5432 -U qa_readonly -d shop
Доступи (адресу, логін, пароль) дає команда — зазвичай DevOps або тимлід. Два правила гігієни: паролі до баз не хардкодять у тестах і не комітять у репозиторій (це секрети), а для автотестів параметри підключення беруть з оточення. До підключення з коду поруч із Playwright ми ще повернемося у главі Тестові дані і БД в автотестах.
Read-only дисципліна на стейджі й проді
Це найважливіший абзац глави для trainee. Правило звучить просто: на спільних стендах, і особливо на проді, тестувальник тільки читає. Причина — у природі середовища. Прод містить реальні дані реальних користувачів; помилкова зміна там миттєва, видима всім і часто необоротна. Немає кнопки Ctrl+Z для бази, у якій ти щойно зіпсував тисячу рядків.
Класична катастрофа — модифікуючий запит без умови. UPDATE чи DELETE без WHERE зачіпає всі рядки таблиці, а більшість клієнтів застосовують зміну одразу (autocommit увімкнений), тож відкотити нема як. Одна забута умова — і всі замовлення в базі змінили статус. Саме тому команди зміни даних (INSERT, UPDATE, DELETE) — окрема тема з окремою дисципліною, глава Зміна даних: INSERT, UPDATE, DELETE.
Чому SELECT безпечний? Бо він нічого не змінює — лише читає. Тому переважна більшість SQL у роботі QA — це SELECT, і починати вчитися теж варто з нього (SELECT: вибірка, фільтрація, сортування). Практичні звички, які варто виробити з першого дня:
- Проси окремий обліковий запис лише для читання (read-only user). Тоді СУБД фізично не дасть тобі щось змінити — найнадійніший запобіжник.
- Якщо тобі таки треба щось записати заради тесту — роби це лише на своїх ізольованих даних на dev/тест-середовищі, ніколи не на спільному стенді наосліп.
- Перед будь-яким модифікуючим запитом спочатку виконай
SELECTз тією самою умовоюWHERE— подивися, які саме рядки зачепиш. Побачив 5000 замість 1 — зупинися. - Знай про транзакції як страхувальну сітку (
BEGIN…ROLLBACK), але пам'ятай, що при autocommit вона не спрацює автоматично — механіку розбирає глава Транзакції та ACID.
І застереження, щоб не було хибного відчуття безпеки: read-only — це не «нульовий вплив». Важкий запит без обмеження на великій прод-таблиці може навантажити базу й зачепити живих користувачів. Тож на проді додавай LIMIT, не запускай важке в пік навантаження й, де можна, читай з репліки, а не з основної бази.
Діалекти SQL коротко
SQL стандартизований (ISO/IEC 9075), і ядро мови — SELECT, WHERE, JOIN, GROUP BY — приблизно однакове всюди. Але кожна СУБД має свій діалект: на краях синтаксис розходиться, і запит з однієї бази може не завестися в іншій. Ось найчастіші розбіжності, на які натикаються:
| Що | PostgreSQL | MySQL | SQL Server |
|---|---|---|---|
| Пагінація | LIMIT 10 | LIMIT 10 | TOP 10 / FETCH FIRST |
| Автоінкремент | SERIAL / IDENTITY | AUTO_INCREMENT | IDENTITY |
| Конкатенація рядків | `'a' | 'b'` | |
| Лапки для імен | "column" | `column` | [column] |
Практичний висновок один: перш ніж писати запит, знай, з якою СУБД працюєш. Запит із LIMIT впаде в SQL Server, а || у MySQL за замовчуванням означатиме не склеювання рядків, а логічне «або». У цьому розділі базовою СУБД для прикладів буде PostgreSQL (безкоштовна, дуже поширена в вебі), а там, де MySQL поводиться інакше, ми це окремо позначатимемо.
Типові помилки
- Виглядає як баг застосунку, а насправді ти дивишся не в ту базу. Дані «не збігаються» — а підключення вказує на dev замість stage, або на репліку, куди свіжий запис ще не доїхав. Перше, що варто перевірити при розбіжності, — до якого середовища й до якої бази ти реально під'єднаний.
- Виглядає, ніби запис зник, а насправді це soft-delete. Через UI елемент пропав, але в базі рядок на місці — просто виставлено
deleted_atабоis_active = false. Застосунок його ховає, база — ні. - Виглядає, що
SELECTбезпечний завжди, а насправді ні. Змінити дані він не змінить, але важкий запит безLIMITна прод-таблиці в мільйони рядків може підвісити базу для живих користувачів. Read-only не дорівнює no-impact. - Виглядає, що SQL один для всіх, а насправді це діалект. Запит, скопійований зі StackOverflow під іншу СУБД, падає з незрозумілою помилкою синтаксису — бо там був
TOP, а тут потрібенLIMIT. - Виглядає, що NoSQL — це «база без правил», а насправді просто інші правила. Відсутність фіксованої схеми не означає, що будь-які дані валідні; вона означає, що перевірку форми даних застосунок робить сам — і саме там ховаються баги «поле раптом відсутнє».
Підсумок
- База даних — джерело істини. UI й API вже інтерпретують дані; коли цифри не збігаються, остаточну відповідь дає SQL напряму в базі.
- СУБД — це сервер, до якого можна під'єднатися напряму. Клієнтом (psql, DBeaver, DataGrip) в обхід застосунку, маючи набір параметрів: host, port, database, user, password.
SELECTчитає й нічого не змінює — це безпечна й найчастіша операція QA;UPDATE/DELETEбезWHERE— необоротна катастрофа.- На проді й спільних стендах — тільки читання, бажано з read-only облікового запису; read-only все одно не означає нульовий вплив на навантаження.
- SQL стандартизований, але має діалекти: ядро переносне, краї (пагінація, автоінкремент, конкатенація) різняться між PostgreSQL, MySQL і SQL Server.
Що питають на співбесіді
- «Навіщо тестувальнику SQL? Коли ти лізеш у базу?» Інтерв'юер хоче почути не визначення, а сценарії: звірити дані з UI, підготувати стан, розслідувати баг, побачити приховане. Сильна відповідь — про базу як джерело істини.
- «Що безпечніше —
SELECTчиUPDATE, і чому на проді треба бути обережним?» Тут перевіряють здоровий глузд і повагу до продакшену. Червоний прапорець для інтерв'юера — кандидат, який спокійно каже «та я просто зроблюDELETE». Правильно — згадати read-only дисципліну й пасткуWHERE. - «Чим реляційна база відрізняється від NoSQL?» Очікують: таблиці й фіксована схема проти гнучкої, SQL проти власних API, сильна цілісність проти масштабу/гнучкості. Бонус — що це різні інструменти, а не «старе проти нового».
- «Як ти під'єднаєшся до бази — що для цього потрібно?» Перевірка практичності: host, port, database, user, password; який клієнт; що доступи беруться в команди, а не хардкодяться.
- «У чому різниця між БД і СУБД?» Просте питання-фільтр: база — це дані, СУБД — програма, що ними керує.
Джерела
- PostgreSQL: The SQL Language (tutorial) — базовий вступ у SQL від офіційної документації PostgreSQL.
- PostgreSQL: psql — референс консольного клієнта й параметрів підключення.
- MySQL 8.0 Reference Manual — офіційна документація MySQL, зокрема відмінності діалекту.
- DataGrip Help — робота з базами в GUI-клієнті: підключення, запити, редагування даних.
У чому різниця між базою даних і СУБД?
Суть одним реченням: база даних — це самі дані, а СУБД — програма-сервер, що ними керує. База даних (database, БД) — набір таблиць конкретного застосунку: замовлення, користувачі, платежі. СУБД (database management system) — окремий серверний процес, який ці дані зберігає, обслуговує запити й контролює доступ: PostgreSQL, MySQL, SQL Server, Oracle. Фраза «у нас Postgres» — про СУБД; всередині одного такого сервера може жити кілька баз різних застосунків. Практичний наслідок: з'єднання ти встановлюєш із СУБД, а запити адресуєш конкретній базі в ній. Це питання-фільтр, на якому інтерв'юер швидко бачить, чи кандидат орієнтується в термінах.
Навіщо тестувальнику SQL? Коли ти лізеш у базу?
База — це джерело істини: UI й API показують уже оброблений зріз даних — із кешами, фільтрами й обчисленнями на льоту, — а база тримає первинні факти. Інтерв'юер хоче почути не визначення, а живі сценарії. Звірити правду з інтерфейсом — порахувати те саме напряму в базі й порівняти з дашбордом (коли цифра на UI не сходиться з базою, це вже класичний баг). Підготувати тестовий стан — знайти чи виставити потрібний запис швидше, ніж прокликувати весь сценарій руками. Розслідувати баг — побачити, на якому кроці ланцюжок обірвався (запис у users є, а в email_queue немає — значить, проблема в черзі листів, а не в реєстрації). Бачити невидиме — soft-delete, статуси фонових завдань, аудит-лог, які застосунок ховає. І підсилити баг-репорт рядком із бази замість самого скриншота.
Що безпечніше — SELECT чи UPDATE, і чому на проді треба бути обережним?
SELECT безпечний, бо нічого не змінює — лише читає; UPDATE/DELETE змінюють дані, і на проді це необоротно. На проді живуть справжні дані справжніх користувачів: зіпсував — і це одразу видно всім, а «скасувати» для тисячі змінених рядків не існує. Тому левова частка SQL у щоденній роботі QA — саме читання, і навчання логічно починати з SELECT. Червоний прапорець для інтерв'юера — кандидат, який спокійно каже «та я просто зроблю DELETE». Сильна відповідь згадує read-only дисципліну й пастку забутого WHERE.
Чому UPDATE чи DELETE без WHERE — це катастрофа?
Модифікуючий запит без умови зачіпає всі рядки таблиці, а не один потрібний: UPDATE orders SET status = 'cancelled' без WHERE скасує геть усі замовлення в базі. Підступність у тому, що більшість клієнтів застосовують зміну одразу — autocommit увімкнений за замовчуванням, тож окремої команди «підтвердити» немає й відкотити нема як. Одна забута умова — і стан зіпсовано незворотно. Захист простий: спершу прогнати SELECT з тим самим WHERE і глянути на кількість рядків у результаті. Очікував один, а бачиш 5000 — умова надто широка, міняти зарано.
Чим реляційна база відрізняється від NoSQL?
Реляційна база зберігає дані в таблицях (рядки й стовпці) із заздалегідь заданою фіксованою схемою, таблиці пов'язані через ключі, працюють через SQL і дають сильні гарантії цілісності — це PostgreSQL, MySQL, SQL Server, Oracle. NoSQL — парасолька над усім, що влаштоване інакше: гнучка або відсутня схема, зв'язки часто на рівні застосунку, у кожної родини своя мова чи API, а сила — у масштабі, швидкості й гнучкості замість строгої цілісності. Практичний наслідок для QA — інший клас багів: не «порушено зовнішній ключ», а «поле, яке ми чекали, у половині документів просто відсутнє», бо схему ніхто не змушує. Бонус до відповіді — що це різні інструменти під різні задачі, а не «старе проти нового»: багато систем використовують і реляційну базу як основне сховище, і Redis як кеш поверх неї.
Які є основні родини NoSQL і чим вони відрізняються?
NoSQL — не одна база, а кілька різних моделей під різні задачі. Документні (MongoDB) зберігають записи-документи в JSON-подібному форматі з гнучкою схемою — можна класти документи різної форми в одну колекцію. Ключ-значення (Redis) — найпростіша модель «дістань значення за ключем», дуже швидка, типово як кеш. Колонкові (Cassandra) оптимізовані під величезні обсяги й інтенсивний запис. Графові (Neo4j) зберігають вузли й зв'язки між ними — зручно для соцграфів і рекомендацій. Спільне для всіх — гнучкіша схема й легше горизонтальне масштабування, але за це часто платять слабшими гарантіями узгодженості (eventual consistency — «колись узгодиться»).
Де база даних стоїть у клієнт-серверній архітектурі?
Сам застосунок даних не зберігає — він тримає бізнес-логіку, а зберігання делегує окремому процесу, СУБД. Натискання кнопки в UI перетворюється на ланцюжок: сервер застосунку виконує логіку, шле СУБД SQL-запит по мережі й отримує назад рядки. Нерідко СУБД крутиться взагалі на окремій машині й слухає власний мережевий порт — це самостійний сервер зі своєю зоною відповідальності. Для QA важливо, що ця сама СУБД приймає з'єднання не лише від застосунку: власним клієнтом можна зайти повз UI й API прямо до джерела істини й побачити дані без кешу, форматування та прикладних обмежень доступу, які ховають частину даних у застосунку.
Як ти під'єднаєшся до бази — що для цього потрібно?
Потрібен клієнт і набір параметрів підключення. Клієнт — це програма, яка вміє під'єднатися до СУБД і виконати запит: консольний (psql для PostgreSQL, mysql для MySQL) або графічний (DBeaver, DataGrip, pgAdmin); новачку зазвичай простіше в GUI — там видно дерево таблиць, працює автодоповнення, а результат виводиться сіткою. Незалежно від клієнта, набір параметрів однаковий: host (адреса сервера), port (мережевий порт), database (ім'я бази всередині СУБД), user, password і часто sslmode. Ці параметри нерідко пакують в один рядок підключення (connection string) виду postgresql://user:pass@host:5432/db. Доступи — адресу, логін, пароль — дає команда (зазвичай DevOps або тимлід), а не вигадуєш сам; і правило гігієни: пароль — це секрет, у код тестів і в репозиторій він потрапляти не має, автотести читають параметри підключення зі змінних оточення.
Які порти за замовчуванням у популярних СУБД?
Варто знати напам'ять: PostgreSQL — 5432, MySQL/MariaDB — 3306, SQL Server — 1433, Oracle — 1521. Це дефолти, зашиті в клієнти й документацію, тож у типовому рядку підключення до Postgres ти й побачиш 5432. Але це саме значення за замовчуванням: на конкретному стенді адміністратор може повісити СУБД на інший порт з міркувань безпеки чи через кілька інстансів на одній машині. Тому порт завжди беруть із параметрів підключення, які дала команда, а не «бо Postgres же на 5432».
Що таке діалект SQL і чому запит зі StackOverflow може впасти?
У SQL є стандарт (ISO/IEC 9075), тож базові конструкції — SELECT, WHERE, JOIN, GROUP BY — виглядають однаково майже скрізь; розходження починаються на краях, і саме сукупність таких відхилень називають діалектом конкретної СУБД. Найчастіші розбіжності — пагінація (LIMIT 10 у PostgreSQL/MySQL проти TOP 10 у SQL Server), автоінкремент (SERIAL проти AUTO_INCREMENT проти IDENTITY), конкатенація рядків (|| проти CONCAT() проти +) і лапки для імен. Тому запит, скопійований зі StackOverflow під іншу СУБД, падає з незрозумілою помилкою синтаксису: там був TOP, а тут потрібен LIMIT; або || у MySQL за замовчуванням означає не склеювання рядків, а логічне «або». Звідси практичне правило: спершу з'ясуй, яка перед тобою СУБД, і тільки потім пиши запит.
Read-only — це те саме, що нульовий вплив на прод?
Ні, і це поширена ілюзія. Read-only означає, що ти нічого не змінюєш, але не означає, що ти нічого не навантажуєш. Важкий SELECT без обмеження на прод-таблиці в мільйони рядків може з'їсти ресурси СУБД і зачепити живих користувачів — база почне тупити для всіх. Тому на проді обмежуй вибірки через LIMIT, плануй важкі запити поза піком навантаження і, за можливості, ходи на репліку замість основної бази. Тобто безпечний за наслідками ще не означає безкоштовний за навантаженням — повага до прода не зникає від того, що ти «просто читаєш».
Як забезпечити read-only дисципліну на практиці?
Найнадійніший запобіжник — проси окремий обліковий запис лише для читання (read-only user): тоді СУБД фізично не дасть тобі щось змінити, навіть якщо ти помилишся в запиті. Далі йдуть звички: перед модифікуючим запитом — спершу SELECT з тим самим WHERE, щоб побачити майбутній радіус ураження; якщо для тесту таки потрібен запис — тільки на власних ізольованих даних у dev/тестовому середовищі, а не наосліп на спільному стенді. Страхувальною сіткою може бути транзакція (BEGIN … перевірив … ROLLBACK), але вона працює, лише якщо її явно відкрити: з увімкненим autocommit кожен окремий запит фіксується одразу. І на проді читай з LIMIT та з репліки, щоб не навантажити основну базу.
Дані на UI не збігаються з базою. З чого почати діагностику?
Перше питання — не «де баг у застосунку», а «до якого середовища й до якої бази я реально під'єднаний». Дуже часто «дані не збігаються» тому, що клієнт дивиться в dev, хоча тестуєш stage, або читає з репліки, до якої свіжий запис ще не доїхав через затримку реплікації. База рідко буває одна: є основна (primary) і копії для читання (репліки), між якими дані їдуть із лагом, а в мікросервісній архітектурі кожен сервіс може мати власну базу — тож один і той самий факт у різних місцях з'являється не одночасно. Тільки переконавшись, що дивишся в правильну базу правильного середовища, має сенс порівнювати цифри й підозрювати продукт.
У UI запис зник, а в базі він на місці. Що це означає?
Найімовірніше — soft-delete, м'яке видалення. Застосунок часто не стирає рядок фізично, а лише виставляє мітку: deleted_at із датою або is_active = false. Через UI елемент пропадає, бо застосунок фільтрує такі записи, але в базі рядок лишається на місці з усіма даними. Це не баг, а типовий патерн: він потрібен для аудиту, відновлення й історії. Для QA це важливо на двох рівнях — по-перше, «видалення» варто перевіряти саме в базі (чи справді проставилася мітка), по-друге, не сплутати soft-delete зі справжнім зникненням даних під час розслідування.
Що таке репліка й чому той самий факт видно в одному місці, а в іншому ще ні?
Репліка — це копія основної бази, призначена для читання, щоб розвантажити primary від запитів. Запис іде в основну базу, а звідти зміни доїжджають до реплік із затримкою (реплікаційний лаг) — зазвичай мілісекунди, але під навантаженням і більше. Через це виникає вікно неузгодженості: щойно створений запис уже є в primary, але на репліці його ще немає. Практично для QA це означає флак і хибні «баги»: тест створив дані через API (запис пішов у primary), одразу читає їх іншим запитом і потрапляє на репліку, де їх ще нема — і бачить порожньо. Тому при розбіжності варто спершу перевірити, з якої бази читаєш і чи не змагаєшся наввипередки з реплікацією, а не одразу заводити дефект.
Чому паролі до баз не можна хардкодити в тестах?
Пароль до бази — це секрет, доступ до реальних (часто чутливих) даних, і місце йому не в коді. Захардкоджений у тесті пароль потрапляє в репозиторій, у його історію (звідки його не витреш простим видаленням рядка) і стає видимим усім, хто має доступ до коду, — це пряма діра в безпеці. Тому параметри підключення для автотестів беруть з оточення (змінні середовища, менеджер секретів), а не з літералів у файлі. Це ще й практично зручно: той самий тест бігає проти dev, stage чи локальної бази без правок коду — змінюється лише конфігурація оточення, а не запит.
Три ситуації, де база вирішує, чи є баг і чий він: використати базу як незалежний оракул при розбіжності UI та API, не перетворити невинний запит на прод-катастрофу й розпізнати, коли «неправильні» дані — це насправді не той стенд або репліка. Скрізь — що дивитися і чому.
Кейс 1. База як незалежний оракул: UI каже 120, API — 118
Дашборд показує «120 активних замовлень», API у відповіді віддає 118, користувач у чаті клянеться про 121. Хто має рацію — знає лише база. UI й API вже інтерпретують дані: щось відфільтрувалося, щось закешувалося, щось порахувалося на льоту. Тому замість суперечки «чий лічильник правильний» рахуємо те саме напряму в джерелі істини.
-- незалежний підрахунок того самого, що показує дашборд
SELECT count(*) AS active_orders
FROM orders
WHERE status = 'active'
AND deleted_at IS NULL;
Результат бази — це якір, до якого прикладаємо кожну цифру:
| Джерело | Значення | Що означає розбіжність із базою |
|---|---|---|
База (SELECT count) | 118 | Еталон: самі факти, без кешу й форматування |
| Дашборд UI | 120 | Кешує або рахує застарілий зріз — підозра на кеш/агрегацію |
| API-відповідь | 118 | Збігається з базою — тут усе чесно |
| Слова користувача | 121 | Інша роль/фільтр або просто інше середовище |
Що дивитися і чому:
- Умова в
WHEREмає точно повторювати логіку застосунку. Якщо дашборд рахує без урахування soft-delete, а ти забувdeleted_at IS NULL, розбіжність буде рукотворна — не в продукті, а у твоєму запиті. Спершу з'ясуй, що саме застосунок вважає «активним». - Збіг API з базою, а UI — ні локалізує баг у фронтенді або в шарі кешу/агрегації, а не в даних. Це вже конкретний, а не абстрактний баг-репорт.
- Рядок із бази підсилює репорт. «Дашборд показує 120, а
SELECT countу базі дає 118» звучить переконливіше за скриншот — це відтворюваний доказ, а не враження.
Кейс 2. Анатомія катастрофи: UPDATE без WHERE і безпечний воркфлоу
Задача звучить невинно: «переведи замовлення №4471 у статус cancelled на стейджі, щоб перевірити банер». Небезпечний варіант і те, на що він насправді перетворюється:
-- НЕБЕЗПЕЧНО: забули WHERE — скасовано ВСІ замовлення в таблиці
UPDATE orders SET status = 'cancelled';
Autocommit увімкнений за замовчуванням, тож зміна застосовується одразу — окремого «підтвердити» немає, відкотити нема як. Безпечний воркфлоу перевертає порядок: спершу подивитися, кого зачепиш, і лише потім міняти.
-- Крок 1: той самий WHERE, але SELECT — читаємо, кого зачепимо
SELECT id, status FROM orders WHERE id = 4471;
-- Крок 2: переконалися, що це рівно 1 потрібний рядок — тепер міняємо
UPDATE orders SET status = 'cancelled' WHERE id = 4471;
Правило прийняття рішення перед будь-яким модифікуючим запитом:
| Ситуація | Дія |
|---|---|
| Це прод або спільний стенд | Не пишу UPDATE/DELETE взагалі — тільки читаю |
SELECT із тим самим WHERE повернув більше рядків, ніж очікував | Стоп: умова надто широка, розбираюся, поки не змінюю |
| Треба записати заради тесту | Лише свої ізольовані дані на dev/тест-середовищі |
| Потрібна страхувальна сітка | BEGIN … перевірка … ROLLBACK, пам'ятаючи про autocommit |
Що дивитися і чому:
SELECTпередUPDATE— не формальність, а перевірка радіуса ураження. Побачив 5000 замість 1 — забутий чи надто широкийWHERE, і ти щойно уникнув інциденту.- Найнадійніший запобіжник — read-only обліковий запис. Тоді СУБД фізично відхилить
UPDATE, і жодна забута умова не спрацює. Проси такий доступ у команди з першого дня. cancelledчерез SQL — це не те саме, що скасування через застосунок. Прямий запис у базу оминає бізнес-логіку: не спишуться бонуси, не піде лист, не оновиться пов'язана таблиця. Тому запис у базу — інструмент підготовки стану, а не заміна тестуванню сценарію через UI.
Кейс 3. «Дані не збігаються» — а насправді не та база
Тест створює замовлення через API й одразу читає його назад, щоб перевірити статус — і бачить порожньо. Виглядає як втрата даних застосунком. Перш ніж заводити критичний баг, треба відповісти на одне питання: до якого середовища й до якої бази я реально під'єднаний.
-- швидка перевірка, де я насправді
SELECT current_database(); -- та база, що очікую?
SELECT inet_server_addr(); -- той хост/стенд, що очікую?
SELECT count(*) FROM orders WHERE id = 90271; -- запис узагалі є?
Дерево причин розбіжності, від найчастішої до рідшої:
| Симптом | Ймовірна причина | Не баг застосунку, бо |
|---|---|---|
| Свіжого запису немає ніде | Підключення на dev замість stage | Дивишся не в ту базу цілком |
| Запис є в primary, немає при повторному читанні | Реплікаційний лаг: читаєш із репліки | Дані ще не доїхали з основної бази |
| Запис «зник» у UI, але є в базі | Soft-delete (deleted_at, is_active = false) | Застосунок ховає, база тримає |
| Порожньо в одному сервісі, є в іншому | Мікросервіс зі своєю базою | Той факт живе в іншому сховищі |
Що дивитися і чому:
- Реплікаційний лаг — типове джерело флаку, а не баг. Запис пішов у primary, а наступний запит потрапив на репліку для читання, де його ще немає. Симптом упізнаваний: локально (одна база) зелено, у CI проти primary+репліки — плавучі падіння одразу після створення даних.
- Soft-delete легко сплутати зі зникненням. Якщо перевіряєш «видалення», перевіряй саме мітку в базі (чи проставився
deleted_at), а не факт, що елемент зник з екрана — зникнути він може й через фільтр. - Перше — середовище, потім продукт. Поки не переконався, що
current_database()і хост саме ті, що очікуєш, будь-яка розбіжність цифр нічого не доводить: можливо, ти просто дивишся в паралельну реальність.
База, СУБД і архітектура
- Розумію різницю між базою даних (самі дані, таблиці) і СУБД (програма, що ними керує: PostgreSQL, MySQL, SQL Server, Oracle) — підключаюся до СУБД, а працюю з конкретною базою в ній.
- Можу пояснити, де СУБД в клієнт-серверній архітектурі: застосунок тримає бізнес-логіку, а по дані ходить до окремого процесу СУБД мовою SQL по мережі, часто на окремій машині; до тієї ж СУБД QA може під'єднатися напряму в обхід UI й API — до джерела істини без кешу, форматування й прав доступу застосунку.
- Знаю, що база рідко одна: є основна (primary) і репліки для читання із затримкою, а в мікросервісах кожен сервіс може мати власну базу.
Навіщо QA лізе в базу
- Можу назвати сценарії SQL для QA замість визначення: звірити правду з UI (незалежний оракул — порахувати те саме напряму в базі й порівняти з дашбордом), підготувати тестовий стан, розслідувати баг, побачити приховане, підсилити баг-репорт.
- Знаю, що база показує, на якому кроці обірвався ланцюжок (запис у
usersє, а вemail_queueнемає означає проблему в черзі листів, а не в реєстрації).
Реляційні vs NoSQL
- Знаю різницю: реляційна база — таблиці з фіксованою схемою, зв'язки через ключі, SQL, сильна цілісність; NoSQL — гнучка або відсутня схема, зв'язки часто на рівні застосунку, масштаб і швидкість замість строгої цілісності; це різні інструменти, не «старе проти нового».
- Можу назвати родини NoSQL і приклади: документні (MongoDB), ключ-значення (Redis), колонкові (Cassandra), графові (Neo4j).
- Розумію наслідок для QA: у NoSQL інший клас багів — не «порушено зовнішній ключ», а «поле, яке чекали, у частині документів просто відсутнє», бо схему ніхто не змушує.
Клієнти й параметри підключення
- Знаю два типи клієнтів: консольні (
psql,mysql) і графічні (DBeaver, DataGrip, pgAdmin) — і чому для новачка GUI зручніший. - Можу перелічити параметри підключення (host, port, database, user, password, часто sslmode) і що вони пакуються в один рядок підключення виду
postgresql://user:pass@host:5432/db. - Впізнаю порти за замовчуванням (PostgreSQL
5432, MySQL/MariaDB3306, SQL Server1433, Oracle1521, на стенді може бути інший) і правила гігієни: доступи бере в команди, паролі до баз не хардкодить і не комітить, для автотестів параметри беруться з оточення.
Read-only дисципліна на проді
- Розумію головне правило trainee: на спільних стендах, і особливо на проді, тестувальник тільки читає — необоротну помилку в проді нема чим відкотити.
- Можу пояснити, чому
SELECTбезпечний (нічого не змінює), аUPDATE/DELETEбезWHERE— катастрофа: зачіпає всі рядки, а autocommit застосовує зміну одразу. - Знаю запобіжники: окремий read-only обліковий запис (СУБД фізично не дасть змінити),
SELECTіз тим самимWHEREперед модифікацією, запис лише на своїх ізольованих dev-даних, транзакціяBEGIN … ROLLBACK(але при autocommit не спрацює сама). - Розумію, що read-only не дорівнює нульовому впливу: важкий запит без
LIMITна великій прод-таблиці навантажить базу й зачепить живих користувачів — томуLIMIT, не в пік, і з репліки.
Діалекти SQL
- Розумію, що ядро SQL (
SELECT,WHERE,JOIN,GROUP BY) стандартизоване й переносне, а краї — діалект, який різниться між СУБД. - Знаю ключові розбіжності: пагінація (
LIMITпротиTOP), автоінкремент (SERIAL/AUTO_INCREMENT/IDENTITY), конкатенація (||/CONCAT/+), лапки для імен. - Можу пояснити, чому запит зі StackOverflow під іншу СУБД падає з помилкою синтаксису — і що перед написанням запиту треба знати, з якою СУБД працюєш.
Типові пастки при розбіжностях
- Розумію, що «дані не збігаються» часто означає не той стенд або репліку з лагом, а не баг застосунку — перше перевіряю, до якого середовища й бази реально під'єднаний; сюди ж реплікаційний лаг як джерело флаку (запис пішов у primary, тест читає з репліки, де його ще немає, і бачить порожньо).
- Знаю soft-delete: у UI запис зник, а в базі рядок на місці з міткою
deleted_atабоis_active = false— застосунок його ховає, база ні.
У чому різниця між базою даних і СУБД?
Питання
База даних vs СУБД — у чому різниця?