Написание интеграционных тестов для 1С-Битрикс
Вы деплоите обновление, и через час обнаруживаете, что импорт из 1С перестал создавать новые товары — изменилась сигнатура метода в модуле catalog, а код интеграции не был обновлён. Интеграционные тесты решают именно эту проблему: они проверяют, что ваш код корректно взаимодействует с ядром Битрикс, БД и внешними системами. Не юнит-тесты в вакууме, а реальные сценарии с реальным ядром.
Чем интеграционные тесты отличаются от юнит-тестов в контексте Битрикс
Юнит-тесты Битрикс-проекта бесполезны без моков половины ядра. Класс, вызывающий CIBlockElement::GetList(), зависит от инфоблоков, БД, кэша, прав доступа. Мокировать всё это — писать второй Битрикс. Интеграционные тесты загружают реальное ядро, работают с реальной (тестовой) БД и проверяют полный цикл.
Типичные сценарии:
- Создание заказа через
CSaleOrder::Add()/\Bitrix\Sale\Order::create()— проверка, что все обработчики событий отрабатывают, скидки применяются, статус корректен - Импорт товаров из XML — проверка маппинга полей, создания разделов, обновления цен
- Формирование выгрузки для 1С — проверка структуры XML, корректности данных
- Обработка webhook от платёжной системы — проверка смены статуса заказа
Настройка тестового окружения
PHPUnit + ядро Битрикс. Битрикс не поставляется с тестовой инфраструктурой, её нужно настроить вручную.
Файл bootstrap.php для PHPUnit:
$_SERVER['DOCUMENT_ROOT'] = '/path/to/site';
$_SERVER['HTTP_HOST'] = 'test.local';
$_SERVER['SERVER_NAME'] = 'test.local';
$GLOBALS['DBType'] = 'mysql'; // или pgsql
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
define('BX_NO_ACCELERATOR_RESET', true);
define('STOP_STATISTICS', true);
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
Константы NO_KEEP_STATISTIC и STOP_STATISTICS отключают запись статистики, NOT_CHECK_PERMISSIONS — проверку прав (иначе тесты будут зависеть от текущего пользователя).
Тестовая БД. Два подхода:
- Отдельная БД — копия продакшн-схемы без данных. Безопасно, но требует поддержки синхронизации схемы.
-
Транзакции — каждый тест оборачивается в транзакцию, которая откатывается в
tearDown(). Быстро, но не работает для тестов, которые сами используют транзакции (вложенные транзакции в MySQL ведут себя неочевидно).
Рекомендуемый подход для Битрикс — транзакции с фоллбэком на ручную очистку:
protected function setUp(): void
{
\Bitrix\Main\Application::getConnection()->startTransaction();
}
protected function tearDown(): void
{
\Bitrix\Main\Application::getConnection()->rollbackTransaction();
}
Структура тестов
Размещайте тесты в /local/tests/ с зеркальной структурой:
/local/tests/
Integration/
Catalog/
ImportTest.php → тесты импорта товаров
PriceCalculationTest.php
Sale/
OrderCreationTest.php
DiscountTest.php
Exchange/
OneCExportTest.php
bootstrap.php
phpunit.xml
phpunit.xml:
<phpunit bootstrap="bootstrap.php">
<testsuites>
<testsuite name="Integration">
<directory>Integration</directory>
</testsuite>
</testsuites>
</phpunit>
Написание тестов: паттерны для Битрикс
Тест создания заказа:
public function testOrderCreationWithDiscount(): void
{
// Arrange: создаём тестовый товар и скидку
$productId = $this->createTestProduct('TEST-001', 1000);
$discountId = $this->createTestDiscount(10); // 10%
// Act: создаём заказ
$order = \Bitrix\Sale\Order::create('s1', 1);
$basket = \Bitrix\Sale\Basket::create('s1');
$item = $basket->createItem('catalog', $productId);
$item->setFields(['QUANTITY' => 1, 'CURRENCY' => 'RUB', 'PRODUCT_PROVIDER_CLASS' => '\CCatalogProductProvider']);
$order->setBasket($basket);
$order->doFinalAction(true);
$result = $order->save();
// Assert
$this->assertTrue($result->isSuccess());
$this->assertEquals(900, $order->getPrice()); // 1000 - 10%
}
Тест импорта XML:
Не вызывайте CIBlockCMLImport напрямую — он тяжёлый и плохо контролируемый. Тестируйте ваш код-обёртку, который вызывает API Битрикс:
public function testProductImportCreatesElement(): void
{
$importer = new \Project\Import\ProductImporter(CATALOG_IBLOCK_ID);
$result = $importer->import([
'XML_ID' => 'TEST-IMPORT-001',
'NAME' => 'Тестовый товар',
'PRICE' => 500,
]);
$this->assertTrue($result->isSuccess());
$element = \CIBlockElement::GetList(
[],
['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'XML_ID' => 'TEST-IMPORT-001'],
false, false, ['ID', 'NAME']
)->Fetch();
$this->assertNotFalse($element);
$this->assertEquals('Тестовый товар', $element['NAME']);
}
Обработка событий в тестах
Обработчики событий Битрикс (OnBeforeIBlockElementAdd, OnSaleOrderBefore, и т.д.) — источник неожиданного поведения в тестах. Обработчик, зарегистрированный в init.php, срабатывает и в тестовом окружении.
Два подхода:
- Принять как данность — тест проверяет систему целиком, включая обработчики. Это правильнее с точки зрения интеграционного тестирования.
-
Временно отключить —
RemoveEventHandler()вsetUp(), восстановить вtearDown(). Используйте, когда обработчик вызывает внешний сервис (отправка email, запрос к API).
Что не стоит тестировать интеграционно
- Вёрстку и визуальное отображение — это задача для E2E-тестов (Playwright, Selenium)
- Чистую бизнес-логику без зависимостей от Битрикс — это юнит-тесты
- Производительность — интеграционные тесты медленные по определению, для бенчмарков используйте отдельный фреймворк
Сроки написания тестов
| Покрытие | Объём | Срок |
|---|---|---|
| Критический путь (оформление заказа, импорт) | 15-25 тестов | 3-5 дней |
| Основная функциональность | 50-80 тестов | 1-2 недели |
| Расширенное покрытие (edge cases, ошибки) | 100+ тестов | 3-4 недели |
Начинайте с тестов на те места, где чаще всего ломается. Обычно это импорт/экспорт 1С и расчёт цен с учётом скидок.







