Каждый разработчик и команда разработчиков должны принимать решения при создании нового проекта. Сегодня обсудим проекты Node.js. Если говорить о разработке на JavaScript, то одним из лучших решений, которое вы можете принять, является выбор TypeScript. Этот язык дает дополнительные инструменты для написания более понятного и поддерживаемого кода.
Еще одно удачное решение — добавить в проект тестовый фреймворк или библиотеку. Сейчас наиболее часто используемым фреймворком является Jest. Его выбор вполне оправдан, поскольку он снабжен мощной встроенной библиотекой для проверки утверждений.
И последнее, но не менее важное — это конвейер. Он поможет избежать ошибок и обеспечит слаженную работу перед каждым слиянием и релизом. Для этой задачи воспользуемся Jenkins, хотя тут сгодится любой другой вариант.
Итак, вы уже знакомы с тремя главными “героями” этой статьи: TypeScript, Jest.js и Jenkins. Приступим.
Проблема
Все началось с того, что меня попросили обновить зависимости проекта, поскольку были обнаружены некоторые уязвимости и прошло много времени с момента последнего обновления. Я обновил следующие зависимости (упомяну наиболее важные):
typescript from 3.9.7 to 4.7.4
jest from 26.4.2 to 28.1.1
ts-jest from 26.3.0 to 28.0.5
Затем, когда я ввел изменения и Jenkins запустил тесты, я столкнулся с двумя разными проблемами на двух этапах.
- На этапе тестирования происходило застревание на случайном тестовом примере при каждом выполнении конвейера, что мешало завершению тестирования.
- После реализации обходного решения для первой проблемы и слияния изменений в другую функциональную ветку с дополнительным кодом и тестами, в лог выводилась следующая ошибка: JavaScript heap out of memory error (Ошибка нехватки памяти в куче JavaScript).
Кроме того, важно отметить, что до обновления зависимостей этап тестирования занимал 1,59 минуты для модульных тестов и 3,26 минуты для e2e-тестов. Запомните эти показатели — мы вернемся к ним чуть позже.
Решение проблемы застревания во время выполнения тестовых примеров
Как видно из ответов на этот вопрос на Stack Overflow, наиболее распространенный подход к исправлению данного сбоя связан с количеством потоков, используемых Jest для запуска тестов. Вы можете изменить поведение по умолчанию, используя две различные опции.
— runInBand
последовательно запускает все тесты и использует основной процесс вместо создания дополнительных потоков.— maxWorkers
позволяет настраивать количество потоков, которые можно задействовать для выполнения тестов. По умолчанию это значение равно количеству ядер, доступных на вашем компьютере за вычетом одного для основного потока.
Поэтому я добавил опцию -runInBand
в скрипт тестирования package.json
, чтобы не позволить Jest запускать более одного теста одновременно, и таким образом уменьшил объем памяти, требуемый для выполнения этого процесса. Однако теперь процесс будет выполняться дольше. Мы решили первую проблему, но предстоит еще разобраться с ошибкой JavaScript heap out of memory.
Копаем глубже в поисках первопричины ошибки “out of memory”
Чтобы лучше понять проблему, первое, что я сделал, — это получил больше информации о профилировании при выполнении теста. Для этого я обновил скрипт test package.json
следующим образом:
{
"scripts": {
"test": "node --expose-gc ./node_modules/.bin/jest --runInBand --logHeapUsage",
}
}
Это позволило мне запускать тесты по отдельности и получать информацию о том, сколько памяти выделялось после запуска каждого теста.
В этот момент я понял, что Jest потребляет больше памяти, чем доступно в контейнере Jenkins, и что для запуска первого теста потребовалось много времени.
Моим первым решением было “разогнать” тест с помощью SWC, который должен был ускорить компиляцию в 20 раз по сравнению с Babel. Однако я столкнулся с несколькими проблемами во время настройки, поскольку оказалось, что он неспособен на должную обработку циклических зависимостей.
Затем в процессе поиска в Google другого варианта я нашел опцию конфигурации ts-jest, которая называлась Isolated Modules. Если говорить вкратце, она отключает проверку типов TypeScript, что приводит к меньшему потреблению памяти и меньшим временным затратам.
Окончательное решение
Полагаю, у вас есть опасения по поводу проверки типов, ведь мы не используем TypeScript, чтобы можно было ее отключить вначале. Но не беспокойтесь, мы просто отключим ее в среде CI. Как вы можете видеть в приведенных ниже скриптах, нам нужно отключить проверку типов для CI-скрипта, отправив переменную среды. Затем необходимо только убедиться в том, что вы используете test:ci
при настройке конвейера.
pipeline {
agent { docker { image 'node:18.7-alpine' } }
stages {
stage('test') {
steps {
sh 'npm run test:ci'
}
}
}
}
const isolatedModules = process.env.ISOLATED_MODULES === 'true';
module.exports = {
// [...]
globals: {
'ts-jest': {
isolatedModules
}
}
};
{
"scripts": {
"test": "jest",
"test:ci": "ISOLATED_MODULES=true node --expose-gc ./node_modules/.bin/jest --runInBand"
}
}
Ниже прилагаю два скриншота: обычное тестирование и CI-тестирование. Сравните результаты.
Если мы посмотрим на три этапа на изображениях выше и проведем сравнение между каждым из них, то увидим следующее.
- Выполнен первый тест: в CI-скрипте время запуска и потребляемая память составляют на 3065 с и на 170 МБ меньше соответственно.
- Выполнен последний тест: окончательный размер кучи в CI-скрипте меньше на 167 МБ.
- Итог выполнения: процесс выполнения тестирования длился на 11 375 с меньше в CI-скрипте.
Наконец, мы видим, что после оптимизации тесты выполняются более чем на 50% быстрее в среде CI.
Читайте также:
- Как настроить Next.js на TypeScript, чтобы получить оценку в 100% от Google Lighthouse и Vercel Analytics
- Как использовать типы пересечения в TypeScript: советы от профессионала
- Различия между псевдонимами типов и интерфейсами в TypeScript 4.6
Читайте нас в Telegram, VK и Дзен
Перевод статьи Carlos Fernando Arboleda Garcés: How To Improve the Jest Performance in CI Environments When Using TypeScript