Вступление
На одном проекте в сфере логистики модуль расчёта доставки выдавал некорректные тарифы после каждого обновления — из-за отсутствия 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-тестах: мокайте репозитории.
- Игнорирование кэширования результатов: сбрасывайте кэш между тестами.
- Тестирование защищённых методов через рефлексию: выделите логику в публичные методы.







