All streams
Search
Write a publication
Pull to refresh
7
0
Максим Соснов @crazymax11

JavaScript разработчик

Send message

А почему нет?


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

Выглядит круто!


Из непривязанных к библиотеке я знаю msw и nock, но сам использую обычно nock. Надо попробовать mirage.js.

Я использовал атрибуты testId=«sumbit-button».

и к этому тоже есть альтернативное мнение, что не нужно использовать data-test-id для всего подряд.


tldr: пользователи не видят ваших data-test-id, они оперируют кнопками, заголовками и текстами — оперируйте и вы.


По моему опыту, не сильно важно использовать data-test-id или сразу матчится на текст/label/etc в html. Сделать любой из этих селекторов очень просто и занимает очень мало времени, относительно проектирования теста и написания кода.
Хотя для компонентов больше 3х контролов, я обычно описываю контролам data-test-id

По факту функциональный тест из статьи зависит и от разметки (h3, div), и от контента («Найти»), и от endpoint url ('api/search').

Можно сказать и так. По факту тест и должен зависеть от каких-то внутренних имлпементация. Ведь мы хотим, чтобы если мы изменили что-то важное (например url ендпоинта), то тест бы упал. Я не хотел бы опираться на h3, div, "Найти", но пока тестовым фреймворкам нельзя сказать "найди там кнопку, максимально похожую на сабмит, и кликни её", приходится писать как минимум текст этой кнопки.


Я всего-лишь предлагаю не описывать такие вещи (h3, div, "Найти", url) явно в тесте и не делать низкоуровневые тесты. Тогда и тесты будут читаемые, и при правках этих признаков страницы (h3, div, "Найти", endpoint) не придется менять тест, достаточно будет поменять только pageObject, который обновится во всех тестах разом.


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

Я в докладе упоминал, что термин unit — очень неудачный. Для меня, мои функциональные тесты — это юнит-тесты. Но я вокруг себя никого в этом убедить не могу, все говорят, что это интеграционные тесты.


Если у вас проблемы с юнитами, значит вы что-то делаете неправильно. А обмазывание снепшотами, e2e тестами, переворачивание пирамид — это самоуспокоение.

"Если ваша система хорошо покрывается тестами — значит ваша система хорошо спроектирована" — это очень популярный миф.


Есть и обратное мнение, что юнит-тесты не нужны:



Это только те ссылки, которые я без проблем могу найти в своей коллекции ссылок на raindrop.io.


К сожалению, мир тестирования чуть сложнее, чем "юниты-интеграционные-e2e". Если бы мы представляли тестирование в виде пирамиды, это определенно была бы как минимум 3D пирамида, а не простой треугольник.


В реальности вам нужно оценивать, какие тесты на вашу систему дают наибольший профит при наименьших затратах.


Известные проблемы низкоуровневых тестов:


  • мешают рефакторить (делать изменения кода без изменения поведения). В вашем примере я не могу заменить булевый pending на state: 'pending' без изменения тестов. Хотя при функциональных тестах я мог бы поменять эту часть реализации и быть уверенным, что если тесты проходят — значит я совершил безопасный рефакторинг. С рефакторингами сложнее чем изменение поля, например выделение из одной сущности двух разных сущностей, низкоуровневые тесты можно смело выкидывать
  • Низкоуровневных тестов нужно писать много. Кроме самого поведения, как минимум на каждую связь двух модулей нам нужны тесты взаимодействия этих сущностей. Как следствие предыдущего пункта, их нужно будет исправлять при нулевых, с точки зрения функционала, изменениях.
  • Они дают слабые гарантии работы приложения.

В реальности же часто бывает, что сложность написания низкоуровневых тестов и функциональных тестов одинакова. Т.е. нам часто ничего не стоит вместо прямого вызова метода какой-нибудь сущности, переписать немного arrange, act и assert фазы теста так, чтобы мы кликали на кнопку и проверяли html/вызов spy'ев.


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

В последних проектах, в которых я работал, у меня всегда были отделены данные от view. Я всегда мог написать кучу юнит-тестов (тест на сервис, тест на стор, тест на компонент, тест на коннектор, тест на отсылку метрик), но это всегда было нецелесообразно, мешало разработке и не гарантировало работу фичи. В результате экспериментов всегда получалось придти к ситуации "пишем меньше тестов, но уверенности в работе кода больше" и другие разработчики с радостью это перенимали.


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


Главное в этом деле:


  • думать своей головой
  • не упарываться
  • писать тесты. Юниты, e2e, плохие, хорошие — любые, пока они приносят вам пользу.
  • эксперементировать, чтобы найти наилучшие способы тестирования функционала в вашем контексте/проекте.

Постараюсь разбить текст на отдельные вопросы. Надеюсь не ошибусь и не пропущу ничего


Pixel perfect больше не в моде?

А зачем следовать моде? Нужно подходить к вопросу здраво:


  • Вашему проекту или вашим клиентам важен pixel perfect?
  • Дизайнеры сразу делают адаптивные решения, которые идеально живут на реальных устройствах пользователей?

Если да — возьмите инструмент для pixel-perfect сравнения вёрстки и макетов. Возьмите storybook и сделайте историю на каждое состояние макета, найдите/напишите свой аддон для pixel-perfect сравнения скриншота с эталонной картинкой из photoshop.


А если у меня некоторые блоки подгружаются по fetch и встраиваются с анимацией при этом время подгрузки и окончательный рендеринг не всегда заканчивается в одно время, что зависит от межсерверного трансфера?

Можно:


  1. Отделить бизнес-логику от верстки. Тогда компоненты можно ставить в любые ситуации и проверять как они выглядят, прокидывая правильные пропсы
  2. Поднять стаб-сервер который будет отдавать всё быстро. Или использовать вместо стаб сервера msw, который зашивает мок сети в servive worker
  3. Подменить в тестовом окружении (jest\storybook) слой, который делает запросы, чтобы запросы возвращали данные из фикстур и моментально
  4. Настроить инструмент для скриншотов (loki/creevey) так, чтобы они дожидались конца запроса. loki.js из коробки дожидается конца всех асинхронных взаимодействий, если мне память не изменяет.

Зависит от того, что вы хотите проверить. Анимацию, процесс загрузки, конечное состояние, все сразу?


Например можно ли указать вызов модального окна при тестировании?

creevey позволяет писать полноценные тесты в рамках сторибука (с кликами по кнопкам, вводом данных в инпуты, ожиданиями и тп). В других инструментах, как правило, есть способ сказать ранеру, когда история готова к скриншоту.


Либо можно написать функциональный тест на cypress/testcafe/selenium и там сделать скриншот в удобный вам момент.
Можно на уровне пропсов выделить состояние "модалка открыта" и сделать скриншот с открытой модалкой, а в функциональном тесте убедится что модалка открывается по клику.

Я не понял, чем фрактальные тесты отличаются от юнит-тестов без моков.


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


Идея с запуском тестов в порядке от менее зависимых к более зависимым — хорошая. А в современных тест-раннерах есть способ указать, какие наборы тестов после каких должны идти?

Да, конечно.

Откуда у вас такая уверенность, что у них бы получилось лучше, а не хуже? Может они бы вообще не релизнулись.


Если есть примеры проектов — вы покажите — мы посмотрим и убедимся, что на $mol можно делать что-то прикладное и поддерживать в долгосрочной перспективе.

Можно.

Можете сказать, где ищут $mol разработчиков?


Вы по ссылкам принципиально не ходите?

Хожу конечно. По ссылке команда из 30 человек с помощью react напилила кучу кода и зарелизилась. Проект успешно живет уже несколько лет. Ну т.е. видно, что при использовании react в относительно большом проекте можно писать долгоживущие приложения.


Команде tinkoff стоило взять $mol? Получилось бы лучше? Они бы быстрее переписали сайт, быстрее бы доставляли фичи в прод, легче была бы поддержка, сайт быстрее бы открывался?

vintage — вы нерельно круты. Уважаю вас за интересные мысли и упорство, на протяжении такого времени.


Я в свое время даже почуствовал гордость, когда вы, в расшифровке моего доклада, написали, что типа "лучше бы $mol использовали, а не эти ваши вебпаки". Прям как будто автограф легенды получил :)


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


  • опровергать некому
  • доказательств на практике нет
  • сравнения на задачах, не имеющих ничего общего с реальностью.

Выглядит как: "$mol лучше, на нём Counter легче сделать, но это неточно и доказать мне нечем, но мне $mol очень нравится".


Есть ли хоть 1 проект на $mol, в котором есть хотя бы десяток разработчиков и проекту уже более года? Можно ли устроиться на работу $mol разработчиком?

Все приложение поделено на модули. По сути модуль у нас это отдельная страница, например модуль главной страницы. У всех асинхронных чанков имя 1 в 1 как у модуля. Во время сборки мы собираем manifest.json, где зашит мапинг имени чанка в название ресурса.
Например:


{
  "MainPage.css": "css/MainPage.d41d8cd98f00b204e9800998ecf8427e.css",
}

На стороне серверного рендеринга мы достаем из собранного манифеста по имени модуля путь до его css и вставляем соответствующий style тэг в разметку.


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


Надеюсь ответил на вопрос :)

Мы тоже успешно прошли этот квест ;) но только у нас vue.js и готовых решений не было. Если вкратце, со стороны SSR мы отдаем:


  • app.js — ядро приложения
  • common.js — общие части для многих чанков
  • manifest.js — тот самый рантайм вебпака из доклада
  • чанк текущей страницы
  • app.css
  • common.css
  • стили текущей страницы
  • Если есть критические стили для страницы, то их вшиваем в html в тэг <style>, а все стили указанные в предыдущих пунктах грузим неблокирующим способом. Примерно как описано здесь
сталкивались ли с FOUC при асинхронных стилях и как лечили?

Как я понимаю, вопрос про загрузку стилей при SPA переходе с одного чанка на другой. При SPA переходе мы загружаем стили и JS, если на момент загрузки JS стили еще не загружены, мы ждем не больше 300мс их дозагрузки и рендеримся. Это не полностью решает FOUC, но в целом стало немного лучше.


Для мобильной версии сайта у нас все стили идут инлайном в html разметку, как критический CSS. Там очень мало стилей и мы решили их просто зашить в html, чтобы не иметь никаких проблем с FOUC.

Столько модулей на одну страницу — это конечно же ужасно. Но в бандле мечты конечно же не так ;) Статистика сборки вебпака говорит, что для сборки нашего самого большого приложения задействовано 10 000 модулей. Само же приложение содержит кучу страниц и эти 10 000 модулей по ним размазаны. В самом докладе собственно про это и рассказывается
Каждый раз, когда нужно собрать приложение.
Локально на машине разработчика проект собирается каждый день, но локальный запуск не такой ресурсоёмкий — в dev сборке нет оптимизаций и можно собирать только нужные для разработки части приложения. В CI проект собирается от нескольких раз до нескольких десятков раз за день.
Спасибо за дополнение про cache-loader, действительно, не знал про него. Как он ведет себя при деплое на продакшен? Не вешает билд?

У него очень простая логика: для каждого реквеста, которые выглядят следующим образомstyle-loader!css-loader!sass-loader!resource-path.scss, сохраняется ответ.
Последующие обращения по этому реквесту будут считываться с файловой системы, вместо выполнения реальными лоадерами. Происходит это во время pitch фазы загрузки модуля.
Алгоритм почти такой же как в babel-loader, но можно прикрутить к любому ресурсу. В ключ кеша входят — request, cacheKay из опций и modifyDate запрашиваемого файла. Мы для CI сделали форк, который использует hash контента запрашиваемого файла вместо modifyDate.
У нас с cache-loader полет нормальный, единственный кто не подводит никогда (в особенности наш форк, проверяющий hash контента)


HardSourcePlugin я в итоге оставил только для дев-сборки, т.к. страшно тащить в продакшен деплой. В деве то себя иногда ведет весьма странно.

Мы в дев сборку наоборот не тащим, в watch режиме по памяти слишком часто начинает падать webpack с HardSourcePlugin. А про время сборки продакшн билдов без него я уже упоминал — 25 минут. На текущий момент, HardSourcePlugin нужен нам как воздух, что плохо, но нас устраивает.


Про абсолютные величины ускорения: я писал изменение (до/после) в секундах, этого показалось не достаточно?

Чисто мое субъективное мнение. Обратил внимание на 8% и подумал что-то не очень помогло, а ведь должно было, и действительно, в цифрах — минута

Мы тоже в свое время ускоряли сборку большого проекта. У нас была сборка по 13-15 минут, и это даже без сорс мапов. Сейчас у нас сборка с сорс мапами холодная — 25 минут, горячая — 2 минуты.


Единственное, что есть в статье и мы не применили это Dll. Но пока и не надо. Остальное все — реально очень полезно и работает!
Дополню нашим опытом:


  • HappyPack нам значительно помог, поэтому не стоит сбрасывать его со счетов. Хотя он неудобный в настройке. Мне не нравятся плагины, генерирующие под себя лоадеры
  • Parallel-webpack наоборот, откатил в скорости. На самом деле webpack и сам умеет собирать массив конфигов. Для нашего случая webpack справляется лучше, чем parallel-webpack. Возможно дело в том, что parallel-webpack для каждого конфига запускает свой процесс, а сам webpack нет, и это позволяет шарить in-memory кеши (результаты обработки модулей) между сборками разных конфигов. Это всего лишь гипотеза, но anyway у нас у конфигов много общего кода и сам webpack оказался быстрее чем parallel-webpack
  • Как-то обошли стороной замечательный cache-loader, который также очень сильно нам помогает. Если протухают кеши HardSourcePlugin, нас спасает cache-loader. Его кеши протухают значительно реже. Сборка вместо 25 минут идет 12-13 минут
  • HardSourcePlugin нужно использовать осторожно, на свой страх и риск. Не со всеми лоадерами\плагинами он может быть совместим и хорошо работать. Мы его чутка форкнули под наши нужды. Из больного — был случай, когда HardSourcePlugin думал что можно отдать данные из кеша, а на самом деле надо было пересобрать. Было, честно говоря, фатально

Также в дополнение, можно указывать не просто % ускорения, а также абсолютные величины. А то выходит что кеши babel-loader ускоряют всего на 8%. Выглядит не очень круто. На моей практике кеши бабеля самые стабильные, простые и одни из самых эффективных.

Ну я про то и говорил.
Чтобы сделать http2 push вам нужно знать, что пушить.
Для того, чтобы знать, что пушить, вам нужен граф зависимостей, чтобы резолвнуть те модули, которые запросит пользователь. Не сказать, чтобы это сильно проще, но это решение, да. Также тут на хабре есть [статья](https://habrahabr.ru/company/badoo/blog/331216/), про то что пока не нужно использовать server push, браузеры его поддерживают по разному и нетривиально его сделать правильно. А если сделать неправильно, то пользователю будет плохо
Ну и есть еще подводные камни, вот пара статей по этому поводу:
* www.contentful.com/blog/2017/04/04/es6-modules-support-lands-in-browsers-is-it-time-to-rethink-bundling
* engineering.khanacademy.org/posts/js-packaging-http2.htm

Этот плагин по умолчанию для каждого асинхронного чанка создает свой css чанк. ExtractTextPlugin же выносит текст только из entry чанка.


Давайте рассмотрим на примере.
У вас есть чанки


  • app.js — entry point
  • common.js — получаемый из commonChunksPlugin
  • mainPage.js — асинхронный чанк с функционалом главной страницы

В случае использования ExtractTextPlugin вы получите на выходе


  • app.css — css entry поинта, которого будет не очень много
  • common.js, mainpage.js — содержат стили внутри js'a через style-loader

В общем-то, в такой схеме ExtractTextPlugin не выглядит особо полезным.


ExtractCssChunksPlugin вообще не имеет опции fallback т.к. она ему не нужна, с помощью этого плагина вы получите следующую структуру:


  • app.css — все тоже самое
  • common.css — все общие стили
  • mainPage.css — стили главной страницы

ExtractCssChunksPlugin — это ИМХО то, как должен работать ExtractTextPlugin по умолчанию.
Минусы этого плагина:


  • Возможно не поддерживает webpack 4 (не проверял ещё)
  • Дефолтное значение filename может мешать, уж много плагин на себя берёт этим значением, мог бы полагаться на то, что указано в секции output
  • Он полагается на ExtractTextPlugin, и это видно в логах. Это само по себе не проблема. Если правильно помню структуру проекта, то проблема в том, что ExtractTextPlugin просто скопипащен (ctrl+c ctrl+v) внутрь ExtractCssChunks. Это, во первых, ужасно. Во вторых, можете забыть о получении фиксов\улучшений ExtractTextPlugin. Поэтому если встретите баг, который был исправлен в ExtractTextPlugin, вероятно он не будет исправлен в ExtractCssChunks.

Но мы используем ExtractCssChunks в продакшне и полёт пока ровный

Немного название неправильно написал, вот он.


Ну конфигурация сверх лоадеров там всего несколько строчек


  • вызов new Plugin({filename: 'mask'});
  • pluginInstance.extract(loadersChain)

Да, отсутствие хорошей поддержки css из под коробки, конечно, немного смущает, тут не поспоришь.

Если вас беспокоит style-loader как fallback для асинхронных чанков, то посмотрите в сторону ExtractCssPlugin. Он выносит весь css из всех чанков.
Не уверен правда, что работает с webpack 4. Но на webpack 3 полёт нормальный.

Information

Rating
Does not participate
Location
Новосибирск, Новосибирская обл., Россия
Works in
Date of birth
Registered
Activity