Большинство модулей Битрикс можно тестировать без установленного продукта. Для этого не нужно имитировать всё ядро: достаточно отделить чистую бизнес-логику и дать минимальные stubs тем API, которые действительно нужны сценарию.
В актуальной документации zr.paidaccess зафиксирован прогон 231 test / 748 assertions. Модуль поддерживает PHP 7.4+, а тестовая матрица охватывает PHP 7.4, 8.1 и 8.2. Это полезная комбинация: нижняя версия выявляет случайное использование нового синтаксиса, верхние — устаревшие вызовы и изменения строгости типов.
Что тестировать без Битрикс
В обычном PHPUnit-процессе хорошо проверяются:
- расчёт периода, суммы и задолженности;
- переходы статусов платежа;
- идемпотентное завершение и отмена;
- проверка подписи и нормализация webhook;
- ledger, возвраты и распределение списаний;
- DTO, presenters и форматирование данных;
- правила зависимостей и production autoload.
Не стоит писать огромную копию ядра ради проверки IncludeComponent. Связку с реальной базой, событиями и административным UI лучше оставить интеграционным smoke-тестам на стенде.
Собственный загрузчик классов
Production map должен быть источником истины и для тестов:
final class ModuleClassLoader
{
public static function register(string $moduleRoot): void
{
$map = require $moduleRoot . '/autoload.production.map.php';
spl_autoload_register(static function (string $class) use ($map, $moduleRoot): void {
if (!isset($map[$class])) {
return;
}
require_once $moduleRoot . '/' . $map[$class];
});
}
}Если provider-классы в production обнаруживаются отдельным loader, тестовый autoload должен повторять это правило либо подключать небольшой autoload.test-extra.map.php. В extra map нельзя складывать все классы подряд: иначе тесты пройдут, а поставка не загрузится на сайте.
Минимальные stubs
Stub реализует только контракт, использованный тестом. Например, для опций:
namespace Bitrix\Main\Config;
final class Option
{
private static array $values = [];
public static function get(string $module, string $name, $default = ''): string
{
return (string) (self::$values[$module][$name] ?? $default);
}
public static function set(string $module, string $name, string $value): void
{
self::$values[$module][$name] = $value;
}
}Этот пример требует PHP 7.4 из-за типизированного свойства. Он не пытается воспроизвести внутреннее хранение Битрикс. Если сервису нужен десяток методов Option, это сигнал выделить собственный интерфейс настроек.
Тест через контракт
Предпочтительнее подменять репозиторий модуля, а не DataManager:
final class InMemoryPaymentRepository implements PaymentRepository
{
public array $items = [];
public function save(Payment $payment): void
{
$this->items[$payment->id()] = $payment;
}
}Тест создаёт сервис с этой реализацией, запускает сценарий и проверяет итоговое состояние. D7 ORM остаётся в отдельном адаптере; его корректность подтверждают небольшие интеграционные тесты.
Architecture tests
Архитектурный тест — обычный PHPUnit-тест, который читает файлы или использует reflection. Полезно проверять:
- Все пути из production map существуют.
- Доменные namespaces не импортируют
Adminи provider-specific классы. - Внешний API используется только внутри соответствующего adapter.
- Публичный UI не зависит от административного.
- Test-extra map не скрывает пропуск в production map.
Простой контроль карты:
public function testProductionAutoloadFilesExist(): void
{
$map = require MODULE_ROOT . '/autoload.production.map.php';
foreach ($map as $class => $file) {
self::assertFileExists(MODULE_ROOT . '/' . $file, $class);
}
}Такие проверки особенно ценны для модулей, которые устанавливаются копированием: ошибка autoload обнаруживается до выкладки.
Матрица PHP и воспроизводимость
Один и тот же composer test должен работать локально и в CI. Не подключайте bootstrap сайта и не читайте реальные опции. В fixture не должно быть доменов, ключей, персональных данных и дампа production-базы.
Для PHP 7.4 нельзя использовать attributes, enums, constructor property promotion и union types в production-коде. При этом прогон на 8.1/8.2 нужен, чтобы увидеть deprecation и несовместимость библиотек. Версию PHPUnit следует фиксировать через composer.lock.
Практический вывод
Начните не со stub всего Битрикс, а с одного рискованного доменного сценария. Вынесите его из компонента, задайте интерфейс хранения и протестируйте на обычных объектах PHP. Затем добавьте минимальный stub для узкой системной зависимости и architecture test для autoload.
Именно сочетание unit-тестов, архитектурных ограничений и нескольких проверок на реальном стенде даёт устойчивый модуль. Полный набор тестов zr.paidaccess показывает, что значительную часть сложного D7-решения можно проверять без установленного ядра.