Тестирование в 1C Bitrix

—

от автора

Предисловие

Говоря о разработке сайтов с использованием CMS 1C Bitrix вопрос покрытия тестами поднимается редко. Главная причина в том, что большинство проектов обходится штатным функционалом, который предоставляется системой — его сложно (да и, в общем-то, незачем) тестировать.

Но со временем проект разрастается, появляется необходимость интеграции со сторонними сервисами и службами (платежные системы, API служб доставки и другие), либо же разрабатывается все более и более специализированный функционал. И чем дальше, тем больше объем кода, контроль за которым лежит уже на разработчике.
Это и является предпосылкой для внедрения в CMS механизма тестирования.

Процесс подготовки окружения к написанию тестов состоит из нескольких шагов:

  1. установить Composer;

  2. настройка Bitrix для работы с Composer;

  3. установить PHPUnit;

  4. настроить PHPUnit для работы с Bitrix.

Composer

Установка

Установку composer проводим по инструкции.

cd ~ curl -sS https://getcomposer.org/installer -o composer-setup.php HASH=Хеш файла php -r "if (hash_file('SHA384', 'composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

По завершению — в консоли видим сообщение, что установщик скачан успешно:

Installer verified

Переходим к установке:

sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

По окончанию видим сообщение о успешной установке:

Output All settings correct for using Composer Downloading...  Composer (version 2.1.9) successfully installed to: /usr/local/bin/composer Use it: php /usr/local/bin/composer

Всю работу с зависимостями организуем в каталоге local. Инициализируем проект:

cd local composer init

Указываем нужные параметры, подтверждаем.
По завершению у нас появляется файл /local/composer.json с примерно таким содержимым:

{     "name": "myproject/website",     "type": "project",     "authors": [         {             "name": "Andriy Kryvenko",             "email": "krivenko.a.b@gmail.com"         }     ] }

Теперь скажем битриксу, что надо использовать сторонние пакеты, установленные через Composer.
Открываем файл /local/php_interface/init.php (создаем, если не существует) и подключаем файл autoload:

<?php  include_once(__DIR__.'/../vendor/autoload.php');

После этого скрываем каталог /local/vendor/ от системы контроля версий. В файл .gitignore добавляем /local/vendor/*

PHPUnit

Переходим к установке PHPUnit. На прод сервере он нам не нужен, поэтому устанавливаем только в качестве dev зависимости и создаем конфиг-файл. Для этого выполняем в командной строке:

composer require --dev phpunit/phpunit ^9.0 ./vendor/bin/phpunit --generate-configuration

В dev-зависимости был добавлен phpunit, а так же создан файл /local/phpunit.xml
Помимо непосредственно PHPUnit, для более приятного вида результатов тестов я использую пакет sempro/phpunit-pretty-print.

composer require --dev sempro/phpunit-pretty-print ^1.4

Теперь нужно создать файл, который будет использоваться при тестировании для инициализации ядра продукта. Назовем его /local/tests/bootstrap.php

<?php  define("NOT_CHECK_PERMISSIONS", true); define("NO_AGENT_CHECK", true);  $_SERVER["DOCUMENT_ROOT"] = __DIR__ . '/../..';  require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");

Настроим PHPUnit, чтобы использовался наш файл инициализации и наши класс декорации результатов. Откроем файл /local/phpunit.xml и приведем его к следующему виду:

phpunit.xml
<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"          bootstrap="tests/bootstrap.php"          cacheResultFile=".phpunit.cache/test-results"          colors="true"          printerClass="Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"          executionOrder="random"          forceCoversAnnotation="true"          beStrictAboutCoversAnnotation="true"          beStrictAboutOutputDuringTests="true"          beStrictAboutTodoAnnotatedTests="true"          convertDeprecationsToExceptions="true"          failOnRisky="true"          failOnWarning="true"          verbose="true">      <php>         <ini name="memory_limit" value="-1"/>         <ini name="display_errors" value="true"/>     </php>      <testsuites>         <testsuite name="default">             <directory suffix="Test.php">tests</directory>             <exclude>tests/Stubs</exclude>             <exclude>tests/Request</exclude>             <exclude>tests/Response</exclude>         </testsuite>     </testsuites>      <coverage cacheDirectory=".phpunit.cache/code-coverage"               processUncoveredFiles="true">         <include>             <directory suffix=".php">classes</directory>         </include>     </coverage> </phpunit>

И добавим команду для быстрого запуска тестов

composer.json
{     "name": "myproject/website",     "type": "project",     "authors": [         {             "name": "Andriy Kryvenko",             "email": "krivenko.a.b@gmail.com"         }     ],     "require-dev": {         "phpunit/phpunit": "^9",         "sempro/phpunit-pretty-print": "^1.4"     },     "scripts": {         "test": "phpunit"     } }

Теперь при выполнении команды

composer test

будут запускаться все тесты. На этом процесс настройки закончен и можно переходить к написанию тестов.

Перед тем, как продолжить

Для удобства свои классы лучше размещать в каталоге local/classes, примерно в следующем виде:

/local/classes/MyProject/Product.php /local/classes/MyProject/Rests.php

И чтобы не было необходимости подключать их каждый раз вручную — регистрируем в файле /local/php_interface/init.php загрузчик, который будет подключать эти классы автоматически по мере необходимости:

<?php  spl_autoload_register(function($className) {     $className = str_replace("\\", DIRECTORY_SEPARATOR, $className);     include_once $_SERVER['DOCUMENT_ROOT'] . '/local/classes/' . $className . '.php'; });  include_once(__DIR__.'/../vendor/autoload.php');

Пример теста

В качестве примера я покажу реальную ситуацию, ее решение и тесты, которые это решение покрывают (часть кода, не относящуюся непосредственно к преобразованиям, в пример не включаю).

Собственно, ситуация: из 1С на сайт в виде строк передается информация о доступных сроках поставки товара, например:

24 часа-7|до 2 дней-14|до 15 дней-неогр

При покупке до 7 штук — поставим за 24 часа, до 14 штук — за 2 дня, в другом случае — за 15 дней.

Нужно преобразовать их в объекты Leftover для дальнейшего использования. Преобразование выполняем с помощью LeftoverTransformer:

Leftover
<?php  namespace MyProject\Product\Requisites;  class Leftover {     public int $time = 0;      /**      * Доступное количество для данного интервала.      * Если количество равно -1.0 - то подразумеваем, что товара неограниченное количество      */     public float $quantity = 0.0;      public function __construct(int $time, float $quantity)     {         $this->time = $time;         $this->quantity = $quantity;     }      public function isAvailable(): bool     {         return ($this->quantity > 0 || $this->quantity == -1.0);     } }

LeftoverTransformer
<?php  namespace MyProject\Product\Requisites\Transform;  use MyProject\Product\Requisites\Leftover;  class LeftoverTransformer {     /**      * @param string $leftoversString      * @return Leftover[]      * Строку получаем в виде      * 24 часа-7|до 2 дней-14|до 7 дней-22|до 15 дней-неогр      */     public static function transform(string $leftoversString): array     {         $leftovers = [];         $intervals = explode('|', $leftoversString);         foreach ($intervals as $v){             $interval = explode('-', $v);             $intervalValues = [];             foreach ($interval as $k => $part) {                 $intervalValues[] = trim($part);             }              if (!empty($intervalValues[0]) && !empty($intervalValues[1])) {                 $leftovers[] = new Leftover(                     self::getTimeFromString($intervalValues[0]),                     self::getQuantityFromString($intervalValues[1])                 );             }         }          return $leftovers;     }      private static function getTimeFromString(string $timeString): int     {         switch ($timeString) {             case '24 часа':                 $time = 1;                 break;             default:                 $parts = explode(' ', $timeString);                 $time = intval($parts[1]);                 break;         }         return $time;     }      private static function getQuantityFromString(string $quantityString): int     {         switch ($quantityString) {             case 'неогр':                 $quantity = -1;                 break;             default:                 $quantity = intval($quantityString);                 break;         }         return $quantity;     } } 

И покрываем эти классы соответствующими тестами:

LeftoverTest
<?php  namespace MyProject\Product\Requisites;  use PHPUnit\Framework\TestCase;  /**  * @covers Leftover  */ class LeftoverTest extends TestCase {     public function testIsAvailable(): void     {         $leftover = new Leftover(1, 12.0);          $this->assertTrue($leftover->isAvailable());     }        public function testAvailableUnlimited(): void     {         $leftover = new Leftover(1, -1.0);          $this->assertTrue($leftover->isAvailable());     }      public function testUnavailable(): void     {         $leftover = new Leftover(1, 0.0);          $this->assertFalse($leftover->isAvailable());     } } 

LeftoverTransformerTest
<?php  namespace MyProject\Product\Requisites\Transform;  use PHPUnit\Framework\TestCase;  /**  * @covers LeftoverTransformer  */ class LeftoverTransformerTest extends TestCase {     public function testEmpty(): void     {         $this->assertEmpty(LeftoverTransformer::transform(''));     }      public function testLeftoversCount(): void     {         $leftoverString = '24 часа-7|до 2 дней-14|до 7 дней-22|до 15 дней-неогр';         $leftovers = LeftoverTransformer::transform($leftoverString);          $this->assertCount(4, $leftovers);     }      /**      * @param string $leftoverString      * @param int $expectedTime      * @param float $expectedQuantity      * @return void      * @dataProvider leftoversProvider      */     public function testLeftovers(string $leftoverString, int $expectedTime, float $expectedQuantity): void     {         $leftovers = LeftoverTransformer::transform($leftoverString);          $this->assertEquals($expectedTime, $leftovers[0]->time);         $this->assertEquals($expectedQuantity, $leftovers[0]->quantity);     }      public function leftoversProvider(): array     {         return [             '24 часа-7' => [                 '24 часа-7',                 1, 7.0             ],             'до 2 дней-14' => [                 'до 2 дней-14',                 2, 14.0             ],             'до 7 дней-22' => [                 'до 7 дней-22',                 7, 22.0             ],             'до 15 дней-неогр' => [                 'до 15 дней-неогр',                 15, -1.0             ]         ];     } } 

Приведенный пример теста позволяет быть уверенным в том, что при работе с товарами мы всегда точно знаем, доступно ли к покупке то или иное количество товара и в какой срок.


ссылка на оригинал статьи https://habr.com/ru/post/667160/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *