<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
ТЕСТУВАННЯ · MIDDLE

Що таке архітектурні тести і що ними варто перевіряти?

Архітектурний тест перевіряє будову коду, а не поведінку: хто кого імпортує, чи класи final, чи є strict_types, чи не лишився `dd()`. У Pest це `arch()->expect('App\Domain')->not->toUse('Illuminate')`: файли читаються парсером, код не виконується. Ними охороняють межі шарів і модулів, які інакше тихо зникають, а наявні порушення вносять у `ignoring()` явним списком.

У вас домен не має залежати від Laravel. Як ви це гарантуєте, крім ревʼю?
Чим `arch()` у Pest відрізняється від звичайного тесту?
Правило `not->toUse('Illuminate')` зелене, а в доменному класі стоїть `now()`. Чому?
Як увімкнути правило шарів у легасі-проєкті, де воно вже порушене в сорока місцях?
архітектурні тести Pest arch Deptrac PHPArkitect шари

Межі між шарами зазвичай описані в документі: домен не знає про Laravel, контролер не ходить у базу, модуль Billing не читає таблиці модуля Hiring. А потім хтось поспішає й додає в доменний клас Config::get(). Unit-тести зелені, бо поведінка правильна, ревʼю дивиться на логіку, і межа зникає без жодного сигналу. Архітектурний тест перетворює таке правило на перевірку, що падає в CI з назвою класу-порушника.

У Pest це arch() з expect() простору імен і очікуванням: not->toUse(), toOnlyUse(), toOnlyBeUsedIn(), toUseStrictTypes(), а для класів toBeFinal(), toExtend(), toImplement(), toHaveSuffix(). Pest розбирає файли PHP-парсером і нічого не виконує, тож тест не потребує бази й іде за секунди. Залежністю Pest вважає і use класу, і виклик функції, тому expect(['dd', 'dump'])->not->toBeUsed() ловить забуті дебаг-виклики. Для типових правил є пресети php(), security(), laravel(), strict(), relaxed().

Найцінніші правила охороняють те, що дорого відновлювати: домен без фреймворку, Application без Infrastructure, модулі, що спілкуються лише через публічний API, контролери без прямих запитів. Перевіряти смак (суфікс Service у кожного класу, заборона слова Manager) можна, але такі правила частіше дратують, ніж захищають.

У архітектурних тестів є дві відомі діри. Перша: глобальні хелпери Laravel now(), config(), app() не мають простору імен, тож not->toUse('Illuminate') їх не бачить, і доменний клас з now() проходить правило. Їх треба перелічити поруч або взяти білий список toOnlyUse, який дозволяє лише назване. Друга: одруківка в просторі імен (App\Domian) нікого не знаходить, і правило вічно зелене. Тому, написавши правило, його один раз ламають навмисно й дивляться на червоний тест. Окремо стежать, щоб новий модуль не опинився поза правилом: зірочка в App\Modules\*\Domain покриває всі модулі одразу.

Поза Pest ту саму задачу розвʼязують Deptrac, де шари й дозволені напрямки описують у конфігу, і PHPArkitect з правилами на PHP. У легасі-проєкті будь-який з інструментів вмикають одразу, а наявні порушення вносять у явний список винятків (ignoring() у Pest, skip-конфіг у Deptrac): нові класи перевіряються з першого дня, а борг видно і його можна скорочувати.

// tests/Architecture/LayersTest.php

// Домен будь-якого модуля: зірочка бере один сегмент простору імен,
// тож новий модуль потрапляє під правило сам.
arch('domain stays framework-free')
    ->expect('App\Modules\*\Domain')
    ->not->toUse([
        'Illuminate',
        'now', 'config', 'app', 'request',   // хелпери без простору імен
    ]);

arch('application does not reach into infrastructure')
    ->expect('App\Modules\*\Application')
    ->not->toUse('App\Modules\*\Infrastructure');

arch('controllers do not query the database directly')
    ->expect('App\Http\Controllers')
    ->not->toUse('Illuminate\Support\Facades\DB');

arch('no debugging leftovers')
    ->expect(['dd', 'dump', 'ray', 'var_dump'])
    ->not->toBeUsed();

// Готові правила: eval, exec, md5, rand, unserialize...
// Свідомий виняток описаний явно, а не вимкнений пресет.
arch()->preset()->security()->ignoring('md5');
Що архітектурний тест не запускає код: він аналізує залежності за `use`, викликами й сигнатурами, тому не потребує бази й іде секунди.
Типові правила з цінністю: домен не знає фреймворку, Application не лізе в Infrastructure, контролери не ходять у базу напряму, модулі говорять лише через публічний API, немає debug-функцій.
Різниця між чорним списком (`not->toUse`) і білим (`toOnlyUse`), і чому білий строгіший.
Знання дір: глобальні хелпери Laravel (`now()`, `config()`, `app()`) не мають простору імен і проходять повз `not->toUse('Illuminate')`, а одруківка в просторі імен робить правило вічно зеленим.
Альтернативи поза Pest: Deptrac (шари в конфігу) і PHPArkitect, а також те, що правило в тесті живе поруч із кодом і падає в тому самому CI.
Писати правило й жодного разу не побачити його червоним: `expect('App\Domian')` з одруківкою нікого не знаходить і завжди проходить.
Покладатися на `not->toUse('Illuminate')` як на повний захист домену й не додати глобальні хелпери `now`, `config`, `app`, `request` у список.
Перелічувати модулі явно й забути додати новий: його домен ніхто не перевіряє.
Вимикати правило, бо в легасі багато порушень, замість того щоб увімкнути його з явним `ignoring([...])` і скорочувати список.
Перевіряти архітектурними тестами смак (кожен клас з суфіксом Service), а не межі, які справді ламають підтримуваність.
ПОРАДА

Розкажіть, яке правило ви б поставили першим: «домен не імпортує фреймворк і хелпери» з `toOnlyUse` або з повним списком заборон. І додайте практику: щойно написавши правило, я один раз ламаю його навмисно, щоб переконатися, що тест справді червоніє.

оновлено 8 жовтня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Pest розбирає файли й рахує залежності за use, викликами й типами, нічого не виконуючи. Хелпери now(), config() і app() це глобальні функції, тож префікс Illuminate на них не спрацює, і їх додають у список окремо. Одруківка в просторі імен просто нікого не знаходить, і тест проходить, тому правило варто один раз зламати навмисно.