Налаштування PHPUnit-тестів для модулів 1С-Бітрікс
Модуль 1С-Бітрікс без тестів — чорна скринька. Виправили одне — зламали інше. Особливо болюче це в модулях з бізнес-логікою: розрахунок знижок, інтеграції із зовнішніми API, обробка замовлень. PHPUnit для модулів Бітрікс має свою специфіку: ядро Бітрікс потрібно завантажувати (і це повільно), статичні виклики заважають ізоляції, а сам Бітрікс надає власні інструменти тестування для ORM.
Налаштування PHPUnit-тестів для модулів 1С-Бітрікс
Структура тестів у модулі Бітрікс
/local/modules/vendor.mymodule/
lib/
Services/
DiscountService.php
ShippingCalculator.php
Repository/
OrderRepository.php
tests/
bootstrap.php
Unit/
Services/
DiscountServiceTest.php
ShippingCalculatorTest.php
Integration/
Repository/
OrderRepositoryTest.php
phpunit.xml
composer.json
phpunit.xml для модуля Бітрікс
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
requireCoverageMetadata="false"
beStrictAboutCoverageMetadata="false"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">lib</directory>
</include>
</source>
<coverage>
<report>
<html outputDirectory="tests/_coverage"/>
<clover outputFile="tests/_coverage/clover.xml"/>
</report>
</coverage>
</phpunit>
bootstrap.php: два режими
<?php
// tests/bootstrap.php
$bitrixLoaded = false;
// Unit-тести без ядра Бітрікс — швидко
if (getenv('PHPUNIT_NO_BITRIX') === 'true') {
require_once __DIR__ . '/../vendor/autoload.php';
return;
}
// Integration-тести з ядром Бітрікс — повільніше
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
define('BX_WITH_ON_AFTER_EPILOG', false);
define('BX_NO_ACCELERATOR_RESET', true);
define('STOP_STATISTICS', true);
$docRoot = realpath(__DIR__ . '/../../../..');
$_SERVER['DOCUMENT_ROOT'] = $docRoot;
$_SERVER['HTTP_HOST'] = 'localhost';
$_SERVER['SERVER_NAME'] = 'localhost';
require_once $docRoot . '/bitrix/modules/main/include/prolog_before.php';
require_once __DIR__ . '/../vendor/autoload.php';
\Bitrix\Main\Loader::includeModule('vendor.mymodule');
Unit-тест з ізольованою бізнес-логікою
// tests/Unit/Services/DiscountServiceTest.php
namespace Tests\Unit\Services;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Vendor\Mymodule\Services\DiscountService;
use Vendor\Mymodule\Repository\OrderRepositoryInterface;
use Vendor\Mymodule\Repository\UserRepositoryInterface;
class DiscountServiceTest extends TestCase
{
private DiscountService $service;
private OrderRepositoryInterface&MockObject $orders;
private UserRepositoryInterface&MockObject $users;
protected function setUp(): void
{
$this->orders = $this->createMock(OrderRepositoryInterface::class);
$this->users = $this->createMock(UserRepositoryInterface::class);
$this->service = new DiscountService($this->orders, $this->users);
}
public function testNewUserGetsNoDiscount(): void
{
$this->orders->method('countCompletedByUser')->willReturn(0);
$this->users->method('getRegistrationDays')->willReturn(5);
$discount = $this->service->calculate(userId: 1, orderAmount: 5000.0);
$this->assertSame(0.0, $discount);
}
public function testUserWith5OrdersGets5PercentDiscount(): void
{
$this->orders->method('countCompletedByUser')->willReturn(5);
$this->users->method('getRegistrationDays')->willReturn(180);
$discount = $this->service->calculate(userId: 1, orderAmount: 5000.0);
$this->assertSame(250.0, $discount); // 5% від 5000
}
public function testDiscountCappedAt20Percent(): void
{
$this->orders->method('countCompletedByUser')->willReturn(100);
$this->users->method('getRegistrationDays')->willReturn(1000);
$discount = $this->service->calculate(userId: 1, orderAmount: 10000.0);
$this->assertSame(2000.0, $discount); // 20% — максимум
}
}
Integration-тест з ORM Бітрікс
// tests/Integration/Repository/OrderRepositoryTest.php
namespace Tests\Integration\Repository;
use PHPUnit\Framework\TestCase;
use Vendor\Mymodule\Repository\OrderRepository;
class OrderRepositoryTest extends TestCase
{
private static int $testUserId;
public static function setUpBeforeClass(): void
{
// Створюємо тестового користувача
$user = new \CUser();
self::$testUserId = $user->Add([
'LOGIN' => 'test_' . uniqid(),
'PASSWORD' => 'Test123!',
'EMAIL' => 'test_' . uniqid() . '@test.ru',
'ACTIVE' => 'Y',
'GROUP_ID' => [2],
]);
}
public static function tearDownAfterClass(): void
{
// Видаляємо тестові дані
\CUser::Delete(self::$testUserId);
}
public function testCountCompletedOrdersReturnsCorrectNumber(): void
{
$repo = new OrderRepository();
// Створюємо тестові замовлення через Sale API
$this->createTestOrder(self::$testUserId, 'F'); // завершене
$this->createTestOrder(self::$testUserId, 'F');
$this->createTestOrder(self::$testUserId, 'N'); // нове, не рахуємо
$count = $repo->countCompletedByUser(self::$testUserId);
$this->assertSame(2, $count);
}
private function createTestOrder(int $userId, string $status): void
{
\Bitrix\Main\Loader::includeModule('sale');
$order = \Bitrix\Sale\Order::create('s1', $userId);
$order->setField('STATUS_ID', $status);
$order->setField('CURRENCY', 'RUB');
$order->setField('PRICE', 1000.0);
$order->save();
}
}
Запуск тільки unit-тестів (без завантаження Бітрікс)
# Швидкі unit-тести без ядра Бітрікс (секунди)
PHPUNIT_NO_BITRIX=true vendor/bin/phpunit --testsuite Unit
# Інтеграційні тести з ядром (хвилини)
vendor/bin/phpunit --testsuite Integration
# Всі тести з покриттям (потребує Xdebug або PCOV)
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html tests/_coverage
CI/CD конфігурація
# .github/workflows/tests.yml
- name: Run PHPUnit Unit tests
run: |
cd local/modules/vendor.mymodule
PHPUNIT_NO_BITRIX=true vendor/bin/phpunit --testsuite Unit
env:
PHPUNIT_NO_BITRIX: 'true'
Терміни
| Завдання | Терміни |
|---|---|
| Налаштування PHPUnit, bootstrap, конфігурація для модуля | 4–8 годин |
| Unit-тести для бізнес-логіки модуля (до 10 класів) | 1–2 дні |
| Integration-тести з ORM Бітрікс | 1–2 дні |
| Рефакторинг модуля для тестованості + покриття 70%+ | 3–7 днів |







