vyvchy
    Теми розділу

    06 · Автоматизація: стратегія

    Піраміда тестування: рівні, вартість, антипатерни

    Зміст

    Питання «розкажіть про піраміду тестування» звучить майже на кожній співбесіді на AQA — і воно не про завчену картинку з трьох поверхів. Інтерв'юер перевіряє, чи розумієш ти економіку свого набору тестів: скільки їх має бути кожного типу і чому саме така пропорція. Помилишся з формою — і сюїта або повзе по сорок хвилин та червоніє від флаку, або дає фальшиву впевненість, пропускаючи реальні регресії.

    Піраміда тестування (testing pyramid) — це не інструмент і не стандарт, а орієнтир для розкладки автотестів за рівнями. Вона відповідає на одне практичне питання: маючи обмежений час на прогін і на підтримку, як розподілити перевірки, щоб набір ловив дефекти якнайдешевше. Ідею популяризував Майк Кон у книзі «Succeeding with Agile» (2009), а канонічним поясненням стала стаття Мартіна Фаулера про TestPyramid. Розберемо не форму заради форми, а причину, чому форма саме така.

    Рівні тестування: від функції до кліку користувача

    Автотести відрізняються обсягом системи, який кожен із них піднімає, щоб зробити одну перевірку. Що більше застосунку в грі — то повільніше, крихкіше й дорожче. Класично виділяють чотири рівні (у мануальному тестуванні рівні означають трохи інше — компонентний, інтеграційний, системний, приймальний; тут ідеться саме про автоматизовані перевірки).

    • Модульні (unit) — перевіряють одну функцію чи клас в ізоляції, без мережі, диску й бази. Наприклад, що calculateDiscount(2000) повертає 300. Виконуються за мілісекунди, їх тисячі, пише переважно розробник (а AQA — на своїх утилітах і хелперах).
    • Інтеграційні / компонентні (integration / component) — кілька одиниць разом: сервіс із реальною базою, або UI-компонент із його залежностями. Ловлять баги на стиках, яких unit не бачить у принципі.
    • API / сервісні (service) — перевіряють застосунок через HTTP чи сервісний шар, без UI. Швидкі, стабільні, близькі до бізнес-логіки. Для продуктового QA це часто найсолодша точка (докладніше — у розділі про API-тестування).
    • Наскрізні (end-to-end, E2E) — ганяють увесь застосунок через інтерфейс, як живий користувач: браузер, мережа, база, сторонні сервіси. Найдорожчі й найповільніші, зате єдині, хто підтверджує, що всі шматки справді стикуються.
    РівеньЩо піднімаєШвидкістьЩо валить тестХто зазвичай пише
    Unitодну функцію/класмілісекундизміна логіки цієї одиницірозробник
    Інтеграційнийкілька модулів + БД/дереводесятки–сотні мсзміна контракту між модулямирозробник / AQA
    APIсервіс через HTTP, без UIсотні мсзміна контракту ендпоінтаAQA
    E2Eвесь застосунок через UIсекунди–хвилинибудь-що на шляху: верстка, дані, таймінгиAQA

    Одразу застереження про термінологію: слово «інтеграційний» кожна команда розуміє по-своєму, а API-тести хтось називає інтеграційними, хтось сервісними. Фаулер навіть розрізняє «вузькі» й «широкі» інтеграційні тести. Не воюйте за ярлики — домовтеся в команді, що на кожному рівні. На співбесіді важить не мітка, а те, чи розумієш ти обсяг і швидкість кожного типу.

    Unit · тисячі тестів
    мілісекунди, дешеві, точна діагностика

    Інтеграційні / компонентні · сотні

    API / сервісні · десятки
    швидкі й стабільні, без UI

    E2E · одиниці
    повільні, крихкі, дорогі

    Unit · тисячі тестів
    мілісекунди, дешеві, точна діагностика

    Інтеграційні / компонентні · сотні

    API / сервісні · десятки
    швидкі й стабільні, без UI

    E2E · одиниці
    повільні, крихкі, дорогі

    Чому вгору дорожчає

    Форма піраміди — не естетика, а прямий наслідок вартості. Що вищий рівень, то дорожчий тест за чотирма незалежними осями, і вони перемножуються.

    • Швидкість і зворотний зв'язок. Тисячу unit-тестів проганяють за секунди, і розробник бачить регресію ще до коміту. Один E2E піднімає реальний браузер, ходить у мережу й чекає на базу — секунди в кращому разі. Повільна сюїта перестає бути захисною сіткою: її просто не запускають на кожен PR.
    • Локалізація фейлу. Червоний unit показує пальцем на конкретну функцію. Червоний E2E каже лише «десь на шляху від кліку до оплати щось не так» — і ти витрачаєш час, щоб відтворити й локалізувати, перш ніж почати чинити.
    • Флакі (flaky). Що більше рухомих частин — мережа, анімації, таймінги, спільні дані — то більше приводів для нестабільності. E2E флакають на порядок частіше за unit; таксономію й лікування розбираємо в главі про флакі-тести.
    • Підтримка. E2E крихкі до будь-якої зміни інтерфейсу: переверстали кнопку — впав локатор. Тому стратегія стабільних селекторів для верхнього рівня критична, але навіть найкращі локатори не роблять E2E дешевими.

    Практичний висновок один: дефект, який unit-тест ловить за секунду, з червоного E2E доводиться діставати хвилинами, а то й годинами. Тому чим нижче ти зловив баг — тим дешевше він тобі коштував.

    Правило найнижчого рівня

    З вартості випливає головна евристика піраміди — правило найнижчого рівня: перевіряй кожен дефект на найнижчому рівні, де його ще можна зловити. Бізнес-правило знижки живе в unit; те, що ендпоінт кошика його застосовує — в API; а E2E лишається один димовий (smoke), який підтверджує, що користувач взагалі доходить до оплати.

    Звідси — наслідок, який часто формулюють на співбесідах слідом за Фаулером: якщо E2E зловив баг, а жоден нижчий тест не впав, у тебе дірка на нижньому рівні — треба додати unit або API-тест на цю логіку, а не радіти, що E2E «врятував». Дублювати ту саму перевірку на трьох рівнях теж не варто: це вартість без нової впевненості.

    Один інваріант — знижка 15% на замовлення понад 1000 — на трьох рівнях виглядає так:

    // Unit: чиста математика знижки, без мережі й БД
    import { calculateDiscount } from '../src/pricing';
    
    test('знижка 15% застосовується лише понад 1000', () => {
      expect(calculateDiscount(1000)).toBe(0);     // межа: рівно 1000 — без знижки
      expect(calculateDiscount(2000)).toBe(300);   // 15% від 2000
    });
    // API: той самий інваріант через ендпоінт, без UI
    test('кошик віддає total зі знижкою', async ({ request }) => {
      const res = await request.post('/api/cart', {
        data: { items: [{ sku: 'A', price: 2000, qty: 1 }] },
      });
      expect(res.status()).toBe(200);
      const cart = await res.json();
      expect(cart.total).toBe(1700);
    });
    // E2E: один димовий шлях — користувач реально бачить підсумок
    test('знижка видима в кошику', async ({ page }) => {
      await page.goto('/product/A');
      await page.getByRole('button', { name: 'Додати в кошик' }).click();
      await page.getByRole('link', { name: 'Кошик' }).click();
      await expect(page.getByTestId('cart-total')).toHaveText('1700');
    });

    Математику знижки ганяємо десятками unit-кейсів (межі, від'ємні, нуль), проводку через сервіс — кількома API-тестами, а весь наскрізний шлях — рівно одним E2E. Це і є піраміда в дії.

    Так

    Ні

    E2E зловив дефект

    Чи є нижчий рівень,
    здатний зловити той самий дефект?

    Додати тест на нижчому рівні:
    unit / API / інтеграційний

    Це справді системний дефект —
    лишаємо перевірку в E2E

    E2E зводимо до димової перевірки шляху

    Так

    Ні

    E2E зловив дефект

    Чи є нижчий рівень,
    здатний зловити той самий дефект?

    Додати тест на нижчому рівні:
    unit / API / інтеграційний

    Це справді системний дефект —
    лишаємо перевірку в E2E

    E2E зводимо до димової перевірки шляху

    Антипатерни форми: ріжок морозива і пісочний годинник

    Коли правило найнижчого рівня ігнорують, піраміда деформується у дві типові фігури.

    Ріжок морозива (ice-cream cone) — перевернута піраміда: гора E2E й ручних перевірок угорі, майже нічого внизу. Симптоми знайомі кожному: сюїта проганяється десятки хвилин, регулярно червоніє через таймінги, а не через реальні баги, і кожна зміна UI вимагає ремонту десятків тестів. Виникає, коли QA прикручують у кінці процесу, розробники unit-тестів не пишуть, а команда виправдовується «ми тестуємо через інтерфейс, бо так робить користувач». Це найдорожчий спосіб отримати найменше впевненості.

    Пісочний годинник (hourglass) — товстий низ і товстий верх, тонка талія: багато unit і багато E2E, але майже немає API й інтеграційних. Команда перевірила окремі одиниці й наскрізні клікання, але пропустила рівень, де живе більшість інтеграційних багів — стики модулів і контракти сервісів. Дефект стику unit не бачить за визначенням, тож його доводиться ловити дорогим E2E, і талію годинника доводиться постійно роздувати.

    ФормаРозкладкаГоловна біль
    Пірамідабагато unit, менше API, мало E2Eздорова: дешево і швидко
    Ріжок морозивабагато E2E/ручних, мало unitповільно, флакі, дорога підтримка
    Пісочний годинникбагато unit і E2E, тонка серединаінтеграційні баги ловляться найдорожчим рівнем

    Лікування обох деформацій те саме: переносити перевірки вниз. Візьми найповільніші, найкрихкіші E2E й спитай, чи не можна той самий інваріант перевірити на API або unit. Зазвичай можна.

    Testing trophy та інші моделі

    Піраміда — не єдина модель, і сучасні фронтенд-команди часто малюють інакше. Трофей тестування (testing trophy) Кента Доддса має чотири рівні: статичний аналіз (типи, лінтер), unit, інтеграційні (найширший шар) і E2E. Логіка в тому, що для JavaScript-фронтенду інтеграційний тест компонента з його залежностями дає найкраще співвідношення впевненості до вартості — більше, ніж ізольований unit, який мокає пів світу. Девіз трофея — «чим більше твої тести нагадують те, як софтом реально користуються, тим більше впевненості вони дають» (Доддс), а корінь цієї ідеї — відома фраза Ґільєрмо Рауха «Пишіть тести. Не забагато. Здебільшого інтеграційні».

    Є й інші орієнтири: Spotify описали модель «стільник» (honeycomb) для мікросервісів, де найбільший шар — інтеграційні тести, а класичних unit і повних E2E мало. Це не спростування піраміди, а переваження середини для контекстів, де ізольований unit дає мало впевненості: тонка логіка, багато стиків, розподілена система. У тестуванні мікросервісів центр ваги закономірно зсувається до контрактних та інтеграційних тестів.

    Піраміда як орієнтир, а не догма

    Крізь усі моделі проходить один інваріант: дешеві, швидкі, стабільні тести мають переважати, а повільні й дорогі — бути нечисленними й зарезервованими під те, що тільки вони й підтвердять. Це і є суть, а конкретна геометрія залежить від архітектури й ризиків.

    Тому не варто ставитися до трикутника як до закону. Насамперед не існує «правильних» цифр: пропорцію 70/20/10 часто цитують, але ні Кон, ні Фаулер її не проголошували — це радше міф, ніж норма. Немає магічного відсотка E2E; є питання «скільки в нас критичних наскрізних шляхів». Для продуктового QA талія піраміди — рівень API — часто найцінніша: тести там швидкі, стабільні, близькі до бізнес-логіки й не залежать від верстки. Для мікросервісів вага зміщується до контрактів. Для CLI-утиліти без інтерфейсу верхнього шару може не бути взагалі.

    Правильне запитання не «чи схожа моя сюїта на трикутник», а «який найдешевший рівень дасть мені цю конкретну впевненість». Відповідь на нього — і є стратегія, яку ми доводимо до кінця в главах про анатомію автотесту і побудову автоматизації з нуля.

    Типові помилки

    • Виглядає як «повне покриття», а насправді ріжок морозива. Двісті E2E ганяють ті самі бізнес-правила, які мали б жити в unit; сюїта йде сорок хвилин і червоніє через таймінги, а не баги.
    • Виглядає як «unit усе покриває», а насправді ніхто не перевірив стики. Сто відсотків unit-покриття, а на проді падає інтеграція модулів — класична тонка талія годинника.
    • Виглядає як «більше E2E — більше впевненості», а насправді навпаки. Кожен зайвий E2E додає часу й флаку, не додаючи покриття, якого немає нижче. Впевненість дає правильний рівень, а не кількість верхніх тестів.
    • Виглядає як «піраміда наказує 70/20/10», а насправді цифри контекстні. Це орієнтир форми, а не норматив; пропорція залежить від архітектури й ризиків.
    • Виглядає як суперечка «API-тест — це unit чи інтеграційний», а насправді ярлик не важить. Важать обсяг системи, швидкість і вартість, а не те, як команда назвала папку.

    Підсумок

    • Форма важливіша за назви: багато швидких дешевих тестів унизу, мало повільних дорогих угорі.
    • Вгору дорожчає одразу за чотирма осями — швидкість зворотного зв'язку, локалізація фейлу, флакі, підтримка — і вони перемножуються.
    • Правило найнижчого рівня: лови дефект там, де це найдешевше; якщо E2E зловив баг, а нижче нічого не впало — дірка на нижньому рівні.
    • Ріжок морозива й пісочний годинник — дві типові деформації; лікуються перенесенням перевірок униз.
    • Піраміда, трофей, стільник — орієнтири під різні контексти, а не догма; запитуй «який найдешевший рівень дає цю впевненість».

    Що питають на співбесіді

    • «Що таке піраміда тестування?» — інтерв'юер хоче почути рівні плюс причину форми (вартість), а не завчене «unit — service — UI». Назви шарів без пояснення економіки — слабка відповідь.
    • «Що таке ріжок морозива і чим він поганий?» — перевіряють, чи впізнаєш ти антипатерн і чи можеш назвати симптоми: повільність, флакі, дорога підтримка.
    • «Де в піраміді API-тести і чому їх люблять QA?» — очікують «талія: швидкі, стабільні, без залежності від UI, близькі до логіки».
    • «Скільки має бути E2E-тестів?» — пастка на конкретну цифру. Сильна відповідь: стільки, скільки критичних наскрізних шляхів; решту логіки — вниз.
    • «Піраміда застаріла? Що таке testing trophy?» — перевіряють, чи розумієш ти, що форма залежить від контексту, а не чи знаєш модне слово.

    За всіма цими питаннями інтерв'юер шукає одне: чи мислиш ти вартістю й зворотним зв'язком, чи просто переказуєш картинку. Хто пояснює чому форма саме така, той і на проєкті вибудує сюїту, яку не соромно запускати на кожен коміт.

    Джерела