Как проверить, что PHP и JavaScript считают одинаково
Тесты ♡
Многие инструменты на портале считают прямо в браузере, но при этом обязаны работать и без 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 песочница воспроизводит не до конца. То есть идеальной страховки нет, и мем «у меня на машине работает» никуда не делся.
Но любая правка в одном языке без правки во втором теперь подсвечивается сразу, и это уже победа. А как я проверяю сами формулы калькуляторов, лежит в статье про тесты формул.
Комментарии
Буду рада вашим мыслям, идеям и вопросам!
Давайте обсуждать ♡
Делитесь
своими мыслями
— мне очень
это важно ♡