Пример использования PHPUnit в своем модуле для CMS 1C-Битрикс

Сегодня мы рассмотрим пример создания unit-теста для CMS 1С-Битрикс на примере модуля-заготовки. Вы можете склонировать его репозиторий с моего GitHub.

Как установить модуль, описано в файле справки самого репозитория. Для его установки также потребуется копия сайта на данной CMS (демо-версию можно бесплатно скачать с официального сайта и установить на каком-либо локальном сервере или, собственно, задействовать хостинг/VPS).

Перед тем как перейти к деталям, хотелось бы высказать пару мыслей. Некоторые разработчики модулей на данной платформе, по моим наблюдениям, не используют unit-тесты в принципе. Но подумайте вот о чем — если вы выкатываете новый функционал, вы всегда рискуете сломать старый и даже этого не заметить. Чтобы это предотвратить, вы можете вручную тестировать весь возможный функционал полностью, после каждой правки. Или вы можете автоматизировать часть ручного труда и использовать модульные, а также интеграционные тесты. По мере роста приложения вы будете экономить все больше времени, так как вам не придется проверять все аспекты вручную. Конечно, модульные тесты это не панацея, но, как считается, без них чистый код не возможен в принципе. Еще представьте, что ваш модуль приобретен несколькими десятками клиентов (не говоря о сотнях). При этом цена ошибок возрастает, но с авто-тестами их намного легче минимизировать.

Ну а теперь, перейдем к примеру реализации тестов, использованном в упомянутом выше репозитории. Отмечу важные моменты:

  1. Для того, чтобы не было проблем с статическими методами классов из ядра Битрикс при создании тестовых двойников (mock-объектов), мы сделаем над ними классы-обертки. Их можно видеть в директории lib/Wrappers.
  2. На примере класса lib/Example.php видно, что внедрение зависимостей происходит через конструктор. Благодаря такой схеме, мы можем заглушить любой используемый класс Битрикс в тестируемом методе.

Давайте рассмотрим детально сам тест в фаиле tests/unit/ExampleTest.php:

namespace Somefirm\Emptymodule;

use Bitrix\Main\Loader;

class ExampleTest extends BitrixTestCase
{
    public function testExampleMethod()
    {
        // Входные параметры
        // Подготовка параметров для тестируемого метода, 
        // в данном случае используем пустой массив
        $param = []; 

        // Результат для проверки
        // Эталон, с которым будем сравнивать результат работы функции
        $expectedResult = [1, 2]; 

        // Заглушки
        // Тут мы подключаем модуль Битрикс iblock, потому что дальше
        // мы используем CIBlockResult для заглушки 
        // Этого класса нет в Wrappers, он и без обертки работает нормально
        Loader::includeModule('iblock'); 

        // Создаем заглушку для результата ответа CIBlockElement
        $CIBlockResultStub = $this->createMock(\CIBlockResult::class);
        // Тут мы видим массив, элементы которого будут отдаваться при
        // каждой итерации при вызове Fetch() в цикле
        // Последний элемент — false, то есть необходимо завершить цикл
        $fetchResults = [
            [
                'ID' => 1,
            ],
            [
                'ID' => 2,
            ],
            false,
        ];
        // Тут мы создаем заглушку метода Fetch()
        // С помощью метода onConsecutiveCalls() мы сообщаем PHPUnit,
        // что метод должен последовательно вернуть значения массива $fetchResults
        // при каждом новом вызове функции Fetch()
        $CIBlockResultStub->method('Fetch')
            ->will($this->onConsecutiveCalls(...$fetchResults));
        // Создаем заглушку CIBlockElement (через обертку)
        $CIBlockElementStub = $this->createMock(Wrappers\CIBlockElement::class);
        // И добавляем этой заглушке метод GetList(), который должен вернуть
        // другую заглушку $CIBlockResultStub, созданную выше
        $CIBlockElementStub->method('GetList')
            ->willReturn($CIBlockResultStub);

        // Вычисление результата
        // Внедряем подготовленную заглушку через конструктор класса Example
        $object = new Example([
            'CIBlockElement' => $CIBlockElementStub,
        ]);
        // Имитируем работу тестируемого метода exampleMethod()
        $result = $object->exampleMethod($param);

        // Проверка
        // Сравнение эталона с реальным результатом работы функции
        // Если результаты совпадут, то тест пройден
        $this->assertEquals($expectedResult, $result);
    }
}

Итак, из кода выше видно, что мы не просто написали простейший тест, но изолировали реальные классы Битрикс, которые использует тестируемая функция. Делается это для того, чтобы тесты не делали запросы к БД и чтобы они не зависели от наличия нужных данных. Если при рефакторинге этой функции мы случайно что-то сломаем, об этом быстро сообщит unit-тест. А когда покрытие программы тестами достигает значительной величины, наша уверенность в надежности кода существенно возрастает.

Кстати, если отключить заглушки, у нас получится уже интеграционный тест, который тестирует не только текущую функцию, но и ее интеграцию с Битрикс. Однако, для таких тестов необходимо подготовить необходимое состояние БД и потом, при необходимости, вернуть обратно в состояние, которое было до начала теста. Для этого нужно написать соответствующий код, который сделает это перед/после запуска теста. Но это уже выходит за рамки этой статьи.

Какую видеокарту купить в начале 2021 года? (Upd: Точнее можно было купить)

В данный момент этот пост не актуален из-за сильно взлетевших цен на видеокарты. Но в ретроспективе интересно посмотреть какие цены были 5 января 2021 г.
Дополнено 01.03.2021


Задумали купить видеокарту для игр и не только, но не хотите переплачивать за ультра новизну? Возможно вам пригодится мое экспресс сравнение. Методика не слишком совершенная, но простая и порядок величины благодаря ней можно увидеть.

Я взял за основу один из крупных сетевых магазинов и ориентировался на минимальные цены для каждой представленной модели. Название магазина не привожу, так как цели рекламировать кого-то нет. Где то дешевле, где то дороже — это понятно. За оценку производительности взял среднее значение в бенчмарке от Passmark. Понятно что полностью объективных бенчмарков пожалуй нет, но так как целью не является сравнивать AMD с Nvidia, то для общей оценки подойдет. В правой колонке представлена стоимость в рублях за условную единицу мощности видеокарт в данном бенчмарке. То есть чем меньше, тем лучше цена/мощность. Вот что получилось:

Какие видеокарты можно было бы выделить? GT 1030 отметил лишь как ту карту, ниже которой нечего ловить. Конечно именуют ее обычно не более чем «затычкой», но в простые игры можно поиграть комфортно. Или, например, если игры вообще не приоритет, тоже пойдет. Кстати, в некоторых случаях видеокарта такого уровня вообще не нужна, так как встроенное видеоядро некоторых процессоров имеет сопоставимую мощность.

Далее отметим GTX 1650. Как видно из таблицы — эта видеокарта является условным победителем в рейтинге цена/мощность. Причем ее уже можно назвать вполне игровой. Кстати, после нее виден некоторый ценовой скачок. Так что если ее мощности в принципе хватит, то нет смысла переплачивать.

Впрочем добавив примерно еще столько же по деньгам, можно увеличить мощность почти вдвое с помощью GTX 1660 SUPER. Она все еще очень не плоха в нашем условном рейтинге — 2 рубля за единицу passmark. Обычной, не SUPER версии, в магазине на момент обзора не было, так что ее нет и в таблице.

Последнюю карту, которую хочу отметить это RTX 3070. Даже если вам нужна максимальная мощность, но финансы не безграничны, возможно стоит остановится именно на этом варианте. После нее мощность растет не так сильно, а вот цена наоборот весьма стремительно.

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

Переход с Notepad++ на VS Code

Долгие годы верой и правдой служил Notepad++. Доходило до того что я использовал его даже в Linux (через Wine). Несмотря на до сих пор его высокую популярность, все же захотел перейти на VS Сode, и не пожалел. Все таки в Notepad++ несмотря на множество плагинов, функционал маловат и продуктивность из-за этого падает. VS Code же напротив сейчас на гребне волны, и даже из коробки в нем много чего есть. Чего стоит только тесная интеграция с git. Хотя зачастую по старой памяти параллельно открыта обычная консоль git, и кое что все равно приходится делать там. Но, тем не менее, наиболее частые операции — add, commit, checkout, push — теперь можно делать прямо из редактора. Однако трудности перехода все же были. Конечно, они в какой-то мере специфичны для меня, но возможно некоторые решения пригодятся и вам. Перед тем как перейти к деталям, замечу, что установка плагинов в VS Code вынесена на основную панель, а не запрятана в недрах меню (в добавок с недвусмысленной иконкой — собери все из кубиков).

Ну а теперь по порядку:

Метки на полях, с быстрым переходом по ним

В Npp они ставились ctrl+F2 или просто мышкой на полях. Переход по ним через F2. Так как бывает приходится иметь дело с длинными файлами, а так же редактировать параллельно код в двух противоположных концах файла, эта функция очень удобна.

Для VS Code есть несколько плагинов на эту тему. Есть например Numbered Bookmarks, который даже судя по названию позволяет ставить нумерованные метки, но для меня это лишне. Остановился на просто Bookmarks. По умолчанию там ctrl+alt+K — создать метку, ctrl+alt+J переход на предыдущую, а ctrl+alt+L на следующую. Метки сохраняются в папке мета-информации .vccode (это нужно иметь ввиду при отслеживании файлов в git).

Вставка HTML

В Notepad++ у меня за это отвечал плагин WebEdit с настроенными кнопками употребительных тегов на панели. Плагин одинаково хорошо вставляет и оборачивает код.

Для VS Code для простой вставки тега, достаточно написать его название и нажать Enter (зависит от типа файла, и соответствующего авто-завершения). Обертку кода в тег тоже можно сделать без плагинов. Для этого выделяем код для обертки, нажимаем shift+ctrl+P (это универсальная комбинация для вызова многих команд), выбираем действие Emmet: Wrap Individual Lines with Abbreviation (Скорее всего оно будет первое в списке, то есть просто нажимаем Enter), затем пишем название тега и еще раз Enter. Довольно мудрено, поэтому я стал искать плагин. Мне пришелся по душе htmltagwrap. Здесь горячая клавиша по умолчанию — alt+W и сразу же происходит обертка тегом P. Если нужен другой тег, просто пишете его название, например div и тег меняется на нужный. К сожалению при нескольких уровнях вложенности иногда бывает работает не корректно и не прописывается автоматически имя завершающего тега, возможно это исправят позднее.

Определение кодировки

Увы, еще встречается кодировка отличная от UTF-8 кое где, например в том же Bitrix. И нужно корректное определение файлов с ней. Кстати, Notepad++ тоже не всегда корректно определял кодировку и мог спутать windows-1251 с Macintosh.

Чтобы VS Code определял кодировку при открытии файла, нужно включить опцию autoGuessEncoding. Для этого идем в меню File -> Preferences -> Settings. Вводим в поиске эту опцию и ставим галку. Теперь редактор должен определять кодировку сам, иначе придется выбирать ее вручную, нажав на название кодировки справа внизу экрана.

Прочее

По умолчанию стоит темная тема оформления. Но я привык к светлой, возможно и вы захотите выбрать что то иное. Для выбора темы нужно нажать не совсем типичную комбинацию клавиш ctrl+K+T, затем выбрать нужную в выпадающем списке. Мне понравилась предустановленная тема Light (Visual Studio). Для особых ценителей можно установить через плагины тему NotepadPlusPlus Remixed Theme. Видимо создана тем, кто так же переходил с Npp.

Хотя в этой статье не ставится цель осветить все полезные плагины, а лишь те что помогают плавно перейти с Npp, упомяну еще парочку.

  • Git History — позволяет посмотреть историю коммитов, подобно Git GUI. На панели справа вверху появляется иконка для вызова плагина.
  • WakaTime — тайм-трекер. Ведет подробную статистику времени, которое вы провели за кодингом. Требуется регистрация на одноименном сайте — wakatime.com — и последующий ввод Api Key в настройках плагина. Для этого нажимаем универсальное shift+ctrl+p, выбираем WakaTime: Api Key, и вставляем ключ полученный на сайте. Саму статистику можно посмотреть на сайте, кликнув на времени, которое отследил Waka, на нижней панели в редакторе.

Выпадающее меню на CSS без доступа к HTML, без JavaScript

В некоторых онлайн конструкторах сайтов на определенных тарифах очень ограничен функционал (например — Битрикс24). Бывает нет возможности вставить свой JavaScript-код, а так же поменять HTML-код готовых блоков. Такая политика в общем то не удивительна, надо же как-то монетизировать такие продукты.

Что если все же хочется выпадающее меню на сайте, а базовые модули с таким функционалом не доступны. Попробуем сделать чисто на CSS. Бывает, что его добавлять как раз разрешают.

Итак пример структуры одноуровневого меню, которое будем превращать в двухуровневое:

<ul>
<li>First</li>
<li>Second</li>
<li>Third</li>
<li>Four</li>
<li>Five</li>
<li>Sub el 1</li>
<li>Sub el 2</li>
</ul>

И CSS:

ul {
  word-spacing: -.36em;
  display: table;
  position: relative;
  margin: 0;
  padding: 0;
}
li {
  display: inline-block;
  padding: 10px;
  word-spacing: normal;
  height: 20px;
  width: 80px;
  background: gray;
}

ul:hover li {
  display: inline-block;
}

li:nth-child(6),
li:nth-child(7) {
  display: none;
  position: absolute;
  left: 200px;
  background: lightgray;
}
li:nth-child(6) {
  top: 40px;
}
li:nth-child(7) {
  top: 80px;
}
li:nth-child(1):hover ~ li:nth-child(6),
li:nth-child(2):hover ~ li:nth-child(6),
li:nth-child(4):hover ~ li:nth-child(6),
li:nth-child(5):hover ~ li:nth-child(6),
li:nth-child(1):hover ~ li:nth-child(7),
li:nth-child(2):hover ~ li:nth-child(7),
li:nth-child(4):hover ~ li:nth-child(7),
li:nth-child(5):hover ~ li:nth-child(7) {
  display: none;
}

Вот что у нас получилось:

  • First
  • Second
  • Third
  • Four
  • Five
  • Sub el 1
  • Sub el 2

Как видно, выпадающие пункты меню размещены в конце, иначе начинаются проблемы при отображении. Некоторый CSS-код относится к решению проблемы отступов между элементами типа inline-block (В частности word-spacing и display: table у UL). Неудобство, конечно, еще в том что все отступы пунктов выпадающего меню придется прописывать вручную. А для разных разрешений экрана, по видимому, их надо будет настраивать с помощью медиа-запросов CSS.

Но суть в том, что все же есть техническая возможность сделать выпадающее меню для клиентов, которые пока не готовы платить дополнительный ежемесячный взнос за расширенный функционал онлайн-конструктора.

Использование Git, если мало места на хостинге или нет доступа к SSH

Система контроля версий, например Git, одна из таких вещей, без которых чувствуешь себя как без рук. После непродолжительного периода привыкания, конечно. Но рассмотрим специфичную ситуацию, с которой приходилось сталкиваться.

Что если нужно вести разработку сайта, а к SSH доступа нет. Или, например, для хранения репозитория на рабочем сервере нет места. Конечно, речь идет не о крупных проектах. А скорее о небольших сайтах, где экономия иногда идет на всём и используется недорогой shared-хостинг. Повод ли это отказываться от Git? Не обязательно. Хотя конечно некоторые его преимущества будут потеряны.

Суть подхода проста. На локальном компьютере можно вести разработку как обычно, отличается лишь выгрузка на production (рабочий сервер). Обновления можно делать с помощью патчей по FTP. А для формирования патча можно использовать следующий код из консоли (где COMMITHASH — хеш коммита, начиная с которого делаем патч). Команды выполняются из рабочей директории сайта:

mkdir patch
cp --parents $(git diff --no-commit-id --name-only -r COMMITHASH) ./patch

После выполнения этих команд, в папке patch будет сформирован патч, который включает все измененные файлы от указанного коммита до HEAD, с сохранением структуры папок. Остаётся залить содержимое этой директории по FTP на сервер.

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

Сложно представить такой подход в команде разработчиков, но если разработчиков всего два или три, достаточно использовать Git-сервер (или например приватный репозиторий на Bitbucket), через который будет идти обмен данными. А загрузку патчей на рабочий сайт по FTP может осуществлять, по согласованию, один человек.