Это история о том, как фронтенд-инженеры в Podio повысили продуктивность, опыт разработчиков и сократили технический долг, просто отказавшись от Karma и Jasmine в пользу Mocha и jsdom.

Читайте полную историю…

Тестировать сложно - я здесь ничего нового не говорю. Об этом написали тонны людей. Я не хочу говорить о тонкостях тестирования, о том, почему это сложно и что вы можете с этим поделать.

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

Такова была наша ситуация в Podio, и это было не весело;
поэтому мы решили ее изменить.

Эта проблема

Podio - это огромная кодовая база, и интерфейс не является исключением: первая из более чем 38 000 коммитов датируется 7 декабря 2010 года.
Наша тестовая установка была основана на Карме в качестве исполнителя тестов, Жасмин как BDD и библиотеку утверждений и PhantomJS как безголовый браузер для тестирования, и немного магии Grunt поверх него.

Чтобы иметь возможность запустить первый тест на этой настройке, нам нужно было собрать все наши тесты и подготовить их для Karma, загрузить задачу grunt-karma, развернуть Karma, запустить ее для PhantomJS и, конечно же, сам PhantomJS.

Посмотрите на этот снимок экрана с одиночным запуском.
Что вы видите?

Да, вы правильно прочитали это изображение.
Всего 10,861 секунды для проведения теста 0,007 секунды.

Конечно, мы могли бы использовать такую ​​задачу, как grunt-contrib-watch, чтобы каждый раз не перезапускать всю установку, и мы пытались, но по тем или иным причинам она никогда не была достаточно стабильной, чтобы мы могли ее использовать. Я говорю о наблюдателях, умирающих после пары запусков, нескольких запусков PhantomJS и случайных сбоев.

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

Утечки зависимостей и технический долг

Другой большой проблемой этого процесса была внутренняя утечка наших зависимостей и глобальных объектов повсюду, что, среди прочего, со временем оказало значительное влияние на рост нашего технического долга.

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

Слишком много частей устаревшего кода предполагали, что некоторые библиотеки являются глобальными (например, jQuery, момент или подчеркивание), и, поскольку все было объединено вместе, PhantomJS никогда не жаловался на неразрешенные зависимости.

Это был побочный эффект, который фактически мешал нам исследовать различные стратегии объединения в производственную среду.

Решение

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

jsdom: реализация стандартов WHATWG DOM и HTML на JavaScript для использования с Node.js.

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

Последним шагом, необходимым для избавления от PhantomJS, был простой способ создания, сброса и уничтожения экземпляров DOM, которые были изолированы, инкапсулированы и запущены на NodeJS.

j sdomify был создан, и он делал именно это.

Благодаря очень простому API jsdomify позволяет создавать, сбрасывать и уничтожать экземпляр DOM, который будет отображаться как глобальная переменная с помощью jsdom и вести себя точно так же, как настоящий браузер. Только намного быстрее.

Простая реализация тестирования компонента React.js теперь выглядит следующим образом:

Ключ к изоляции здесь заключается в том, что экземпляр DOM создается только во время оценки и до того, как React будет включен на «страницу».
Затем он уничтожается после завершения всех тестов. .

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

beforeEach(() => {
  jsdomify.clear();
})

Результат

он же showMeTheNumbers

Нам потребовалось почти 5 месяцев работы в качестве побочного проекта, чтобы перенести весь набор тестов и исправить все ошибки, которые мы совершали в прошлом.

На первый взгляд мы недооценили необходимые усилия, так как никто из нас не имел четкого представления о том, сколько технического долга нам пришлось выкопать, чтобы добраться до финиша, и, черт возьми, это было много!

Тем не менее, конечные результаты были впечатляющими и определенно того стоили!
Для краткости я назову комбинацию Karma / Jasmine / PhantomJS, Karma и комбинацию Mocha / jsdom просто Mocha.

Изоляция

  • Mocha: каждый тест выполняется изолированно. Каждая неразрешенная зависимость вызывает ошибку. Если не указано иное, никакие два компонента не тестируются в одной модели DOM.
  • Карма: здесь нет изоляции. Все связано на одной странице, и все утекает повсюду

Здесь нет совпадений, у одного оно есть, а у другого нет.

Запуск полного набора тестов

  • Mocha: 1131 тест выполняется за 30,017 с.
  • Карма: 842 теста выполняются за 20,334 секунды (1131 тест примерно за 27,313 секунды).

На первый взгляд Mocha здесь немного хуже, но загвоздка в том, что Mocha уничтожает и создает новый экземпляр DOM для каждого тестового файла, а у нас их 150! Довольно впечатляюще, да?

Одиночный тестовый прогон (скорость)

  • Mocha: один тест выполняется в течение 2,436 секунд, 2,009 из которых используются для настройки среды RequireJS.
  • Карма: мы уже видели это, но напомним, один тест выполняется за 10,861 с, а сам модульный тест занимает всего 0,007 с.

Что ж, если бы мы хотели здесь поиграть с числами, мы могли бы сказать, что с Mocha у нас было увеличение производительности на 445% за один прогон.
Что не так уж и плохо.

Я также хочу подчеркнуть, что создание нового экземпляра DOM для каждого тестового файла требует затрат, которые можно измерить примерно за 3-400 мс.
Однако эта задержка едва ли заметна в любом реальном сценарии использования.

Одиночный тестовый прогон (простота использования)

  • Мокко: у мокко есть уникальная функция, которая позволяет нам передавать параметры grep только для выполнения определенных тестов.
    Стиль кармы [описать / это]. [only / skip], конечно же доступны, но пригодятся только при окончательном выборе (например, при пропуске тестов).
  • Карма: единственный способ добиться этого в Карме (Жасмин) - использовать нотацию iit (ddescribe) или xit (xdescribe), что требует от нас фактического изменения исходного кода.

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

Наблюдатели

  • Mocha: mocha имеет встроенный быстрый наблюдатель, который можно добавить к любому другому параметру, что значительно упрощает жизнь, когда дело доходит до TDD.
  • Karma: с другой стороны, Karma не поддерживает эту функцию. Единственный способ добавить его - использовать поверх него какой-нибудь плагин grunt / gulp / broccoli, который может работать, а может и не работать, как ожидалось. В нашем случае это были grunt-contrib-watch, и они не очень хорошо работали.

Выводы

Мы очень довольны новой комбинацией Mocha / jsdom, с учетом всего вышеупомянутого, технического долга, который мы погасили, и всех уроков, которые мы извлекли на протяжении всего процесса.

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

Не позволяйте этому проглотить.

Если вам понравилась эта история, поразите это маленькое сердце и не забудьте написать об этом в Твиттере. Пока вы занимаетесь этим, почему бы не подписаться на меня в Твиттере?
Я могу сказать еще кое-что!