Вступ
На одному проєкті у сфері логістики модуль розрахунку доставки видавав некоректні тарифи після кожного оновлення — через відсутність unit-тестів. Після рефакторингу та написання тестів помилки зникли, а час на внесення змін скоротився вдвічі. Вартість регресій впала на 70%, а бюджет на підтримку модуля знизився вдвічі. Це типова ситуація: код Бітрікс-модулів часто щільно пов'язаний з ядром, що робить його складним для тестування. Досвід нашої команди в десятках проєктів підтверджує: правильна архітектура та unit-тести скорочують час розробки нових фіч вдвічі та знижують кількість багів на 70%. Ми регулярно стикаємося з проєктами, де бізнес-логіка не ізольована, і написання тестів вимагає попереднього рефакторингу. Однак навіть у таких випадках unit-тести окупаються протягом перших місяців експлуатації.
Чому unit-тести в Бітрікс складніші, ніж в інших проєктах?
Типові проблеми, які ми бачимо на старті:
- Щільна пов'язаність з ядром. Класи успадковують
CBitrixComponentабо викликаютьCIBlockElementнапряму. Будь-який тест вимагає ініціалізації всього оточення. - Відсутність інтерфейсів. Репозиторії часто не виділені в окремі класи. Натомість SQL-запити розкидані по методах.
- Глобальні стани. Бітрікс використовує глобальні змінні (
$APPLICATION,$DB) та синглтони. Це ламає ізоляцію тестів. - Повільний bootstrap. Завантаження ядра через
prolog_before.phpзаймає 1–3 секунди, що робить прогін тисячі тестів неприйнятно довгим.
Через ці фактори багато розробників відмовляються від unit-тестування. Але ми знайшли підхід, який робить його ефективним.
Принцип тестованої архітектури
Перший крок — виділити бізнес-логіку в окремі класи з чіткими інтерфейсами. Не робіть так:
// Бізнес-логіка змішана з інфраструктурою — неможливо протестувати ізольовано public function calculateDiscount(int $userId): float { $user = \CUser::GetByID($userId)->Fetch(); // статичний виклик Бітрікс $orders = \CSaleOrder::GetList([], ['USER_ID' => $userId])->Fetch(); return $orders['count'] > 10 ? 0.15 : 0.05; } Натомість інжектуйте залежності:
// Логіка відокремлена, залежності інжектуються class DiscountCalculator { public function __construct( private UserRepositoryInterface $users, private OrderRepositoryInterface $orders, ) {} public function calculate(int $userId): float { $user = $this->users->findById($userId); $orderCount = $this->orders->countByUserId($userId); return $orderCount > 10 ? 0.15 : 0.05; } } Репозиторії реалізуються через Бітрікс API в продакшн-коді та через моки в тестах. Такий рефакторинг окупається вже після першого циклу змін. Детальніше про впровадження залежностей у документації Бітрікс.
Інфраструктура тестів
Налаштовуємо PHPUnit з двома bootstraps: один для чистих unit-тестів (без ядра, лише Composer autoload), другий — для інтеграційних, що завантажують Бітрікс.
Приклад bootstrap для інтеграційних тестів:
// tests/bootstrap.php define('NO_KEEP_STATISTIC', true); define('NOT_CHECK_PERMISSIONS', true); define('BX_WITH_ON_AFTER_EPILOG', false); define('BX_NO_ACCELERATOR_RESET', true); $_SERVER['DOCUMENT_ROOT'] = realpath(__DIR__ . '/../../../..'); require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php'; \Bitrix\Main\Loader::includeModule('your.module'); Для ізольованих тестів — окремий bootstrap без prolog_before.php. Це прискорює прогін у 10–50 разів.
Приклади unit-тестів
Тест бізнес-логіки (без ядра):
class DiscountCalculatorTest extends TestCase { private DiscountCalculator $calculator; protected function setUp(): void { $this->calculator = new DiscountCalculator( users: $this->createStub(UserRepositoryInterface::class), orders: $this->createConfiguredMock( OrderRepositoryInterface::class, ['countByUserId' => 5] ), ); } public function testLessThan10OrdersGivesBasicDiscount(): void { $this->assertSame(0.05, $this->calculator->calculate(1)); } public function testMoreThan10OrdersGivesPremiumDiscount(): void { $repo = $this->createConfiguredMock( OrderRepositoryInterface::class, ['countByUserId' => 15] ); $calc = new DiscountCalculator($this->createStub(UserRepositoryInterface::class), $repo); $this->assertSame(0.15, $calc->calculate(1)); } } Інтеграційні тести з ядром використовуємо лише для перевірки ORM або складних ланцюжків викликів. Вони запускаються окремо, не в основному наборі.
Який ROI від unit-тестів?
Впровадження unit-тестів скорочує час на налагодження на 70% та знижує вартість підтримки вдвічі завдяки ранньому виявленню регресій. У середньому на одну бізнес-операцію (розрахунок, валідація) йде від 2 до 6 годин написання тестів. Повний цикл для модуля середнього розміру — 2–5 днів. Терміни залежать від поточної архітектури та необхідності рефакторингу. Якщо ви хочете підвищити стабільність проєкту, зв'яжіться з нами для аудиту тестованості.
Порівняння підходів
| Критерій | Ізольовані unit-тести | Інтеграційні з ядром |
|---|---|---|
| Швидкість виконання | 0.01–0.1 сек | 1–5 сек |
| Залежність від БД | Ні | Так |
| Вимагають моків | Так | Ні |
| Покривають бізнес-логіку | Так | Так |
| Покривають інфраструктуру | Ні | Так |
| Простота налагодження | Висока | Середня |
Ізольовані тести в 50 разів швидші — для більшості сценаріїв обираємо їх.
Покриття та пріоритизація
Не потрібно покривати тестами 100% коду. Пріоритети:
| Пріоритет | Що тестуємо |
|---|---|
| Високий | Розрахунки цін, знижок, вартості доставки |
| Високий | Бізнес-логіка станів (статусна машина) |
| Високий | Парсери та маппінг даних (імпорт з 1С, Excel) |
| Середній | Валідатори вхідних даних |
| Середній | Алгоритми формування звітів |
| Низький | Шаблони компонентів, UI-логіка |
Цільове покриття для ключової бізнес-логіки — 80%+. Для інфраструктурного коду (репозиторії, адаптери) достатньо інтеграційних тестів. Це дає баланс між швидкістю та надійністю.
Що входить у написання unit-тестів
- Аудит коду модуля на тестованість, рефакторинг точок впровадження залежностей.
- Налаштування PHPUnit з bootstrap для Бітрікс-оточення.
- Написання тестів для бізнес-логіки: розрахунки, статусні машини, парсери.
- Налаштування звіту покриття через Xdebug.
- Інтеграція запуску тестів у CI (GitHub Actions / GitLab CI).
- Документація із запуску тестів та додавання нових.
Ми гарантуємо якість: досвід наших інженерів — понад 10 років у Бітрікс-розробці. Замовте написання unit-тестів під ключ — отримайте стабільний і легкопідтримуваний код. Оцінимо ваш проєкт безкоштовно, просто зв'яжіться з нами.
Типові помилки при написанні тестів для Бітрікс
- Спроба завантажувати ядро для кожного тесту: використовуйте два bootstrap'и.
- Використання реальної бази даних в unit-тестах: мокайте репозиторії.
- Ігнорування кешування результатів: скидайте кеш між тестами.
- Тестування захищених методів через рефлексію: виділіть логіку в публічні методи.







