Как проверить, что PHP и JavaScript считают одинаково

0

Тесты ♡

Многие инструменты на портале считают прямо в браузере, но при этом обязаны работать и без JavaScript. Отсюда две копии одной логики: одна на JS, вторая на PHP. Пока копия одна, разъехаться ей не с чем. Когда их две, они рано или поздно начинают жить своей жизнью, и об этом ты узнаёшь от пользователя, у которого «тут одно, а там другое». Классика.

Зачем вообще две копии

Если JavaScript включён, человек получает результат сразу, без отправки формы, и это приятно. Если выключен, или на страницу зашёл поисковый робот, то считает сервер. Ответы обязаны совпадать, иначе страница будет показывать то одно, то другое, как кот Шрёдингера.

В PHP-классе Punycode у меня даже висит комментарий: браузерная копия лежит в public/js/packs/code.js, поведение должно совпадать. Но комментарий это просьба к самой себе. Всем известно, как хорошо люди выполняют такие просьбы. Поэтому за совпадением следит тест.

Как устроен тест

Идея простая: гоняем одни и те же данные через обе копии и сравниваем ответы. Для этого нужны три детали.

Файл tests/js/cases.json со списком входных данных: модуль и значения полей формы. Сейчас там 114 случаев на 14 модулей, от base64 и sha256 до css_units, punycode и redirect. Рекордсмен по числу случаев редирект, у него их 25.

Скрипт tests/js/parity.mjs загружает боевые файлы tools.js и наборы из public/js/packs в песочницу node:vm, прогоняет каждый случай через MerkaTools.calculate() и печатает результат в JSON.

Тест PackParityTest запускает этот скрипт, считает те же случаи через PHP-модули и сравнивает оба результата.

Песочница вместо браузера

Настоящий браузер для этого не нужен, слава богу. Боевой JavaScript крутится в vm, а в песочницу я кладу только то, что скриптам действительно необходимо:

const sandbox = {
  console, TextEncoder, TextDecoder, btoa, atob, crypto, Intl,
  Uint8Array, Uint32Array, DataView, Set, Map, Object, Array,
  String, Number, Math, Date, RegExp, Error, Infinity, JSON, navigator: {},
};
sandbox.window = sandbox;
vm.createContext(sandbox);

Приятный побочный эффект: если какой-нибудь инструмент вдруг полезет в document или ещё какую браузерную штуку, он в тесте просто упадёт. Значит, в расчётах нет лишних привязок к окружению. Ловим костыли на взлёте.

Что именно сравнивается

Сравнение строгое, никаких «ну примерно похоже». Из результата берутся заголовок, значение, единица и таблица подробностей, а из неё подпись, значение и статус каждой строки. Если модуль выбросил ошибку ввода, сравниваются тексты ошибок.

$this->assertSame($php, $browser,
    'Расхождение в случае №'.$i.': '.$case['module'].' '.json_encode($case['input'], JSON_UNESCAPED_UNICODE));

Сообщение сразу говорит, где копать: номер случая, модуль и входные данные. Не приходится вручную играть в «найди десять отличий» среди сотни проверок.

А если на машине нет Node?

Тест сначала ищет Node командой where node (Windows) или command -v node. Не нашёл, значит, скромно пропускает сам себя, а не падает с криком. Так что у человека без Node остальные тесты работают как ни в чём не бывало.

Чего этот тест не видит

Он проверяет только те случаи, которые я записала заранее, и только 14 модулей: текстовые и кодовые инструменты. Отличия настоящего окружения вроде локали браузера или полноценного Intl песочница воспроизводит не до конца. То есть идеальной страховки нет, и мем «у меня на машине работает» никуда не делся.

Но любая правка в одном языке без правки во втором теперь подсвечивается сразу, и это уже победа. А как я проверяю сами формулы калькуляторов, лежит в статье про тесты формул.

Комментарии

Буду рада вашим мыслям, идеям и вопросам!
Давайте обсуждать ♡

Пока никто не написал.
Станьте первым! ♡