TheOutloud — сервис коротких аудиоблогов и подкастов

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

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

image

За все это время у меня несколько раз менялась основная идея и предназначение сайта. Как вы помните, изначально я планировал делать его только для озвучки постов и историй, которые любой пользователь мог найти в сети или придумать и залить на сайт. Тогда я видел его скорее как место, куда люди заходят, чтобы послушать последние новости или смешные истории из жизни, некая смесь РИА новости и Пикабу.

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

Тогда я решил найти другой подход к развитию проекта.

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

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

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

Просто представьте: есть Инстаграм для фото и сторис, Youtube для видео, TikTok для коротких видео, а TheOutloud — будет площадкой для обмена аудио файлами.

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

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

Именно в такой ресурс я верю и хочу развивать, поэтому и решился на обновление!

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

Немного дополнилась работа с треками. Теперь можно загрузить свой трек на сайт, а потом добавить ссылкой на свой персональный сайт или блог, по аналогии с видео Youtube. Также теперь можно делиться ссылкой на видео в различных соц сетях в пару кликов. Мне писали, что эта опция многим была очень нужна.

Упрощена загрузка треков на сайт. Теперь не нужно указывать длину трека — меньше 5 минут, больше и так далее. Сайт сам определит его продолжительность и зальет в нужный раздел.

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

Также сайт запоминает место, на котором вы остановились в прослушивании трека. Когда вы откроете его в следующий раз, он начнет играть с последней сохраненной точки.

Ну, и были исправлены другие ошибки в отображении на мобильных устройствах и улучшена скорость работы сайта.

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

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

Посмотреть мой проект вы можете по ссылке —theoutloud.ru

ссылка на оригинал статьи https://habr.com/ru/post/486408/

Котики и Scrum

Введение

Так вышло, что с темой agile и scrum меня связывают долгие и нежные чувства. Все началось ещё в студенчестве: стартапы при полном отсутствии опыта, но с горящими глазами, преакселерационная программа ФРИИ, крутые конференции со спикерами-небожителями. Как часто бывает со стартапами: из наших идей ничего не вышло, но для себя я вынесла главное — бизнес может (а в некоторых случаях должен) быть гибким.

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

Как всё началось

У истоков стояли 3 энтузиаста без практического опыта ведения бизнеса, но с идеей сделать антикафе с котиками. Проект в первую очередь социальный: для души и удовольствия, поэтому основной целью была самоокупаемость. На волнах энтузиазма был составлен бизнес-план, просчитаны вложения, точка безубыточности и прочее, но как только дело дошло до реализации идей, работа по традиции замедлилась…

“Зачем нам твой аджайл?”

Такой вопрос задал мне мой муж (один из трёх энтузиастов) на мои робкие попытки придать ускорение новому проекту. “Мы же и так знаем, что делать, зачем нужны дополнительные встречи и вот это вот всё?” — это, кстати, один из самых популярных вопросов при внедрении новых подходов к работе, актуальный как для IT, так и для не IT- компаний, так что запомним его.

Один из способов отработки этого возражения — показать перспективы развития (точнее, стагнации) при текущем подходе. Что ж, когда в команде 3 организатора, почти неизбежно каждый захочет переложить ответственность на другого. Так что за несколько недель мы не слишком сдвинулись с мертвой точки. Самоорганизация не сработала. Пришлось ей помочь.

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

Какие шаги мы предприняли на этом этапе:

  1. Завели и приоритизировали бэклог задач

    Сначала мы выделили основные блоки задач (поиск и аренда помещения, ремонт, маркетинг, дизайн и т.д.), потом уже расписали каждый из блоков на отдельные задачи и определили порядок их исполнения. Уже этот простой шаг помог навести порядок в голове и сделать приблизительные оценки сроков.

  2. Настроили ритм взаимодействия

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

Так что нами было проведено собрание, на котором было принято решение:

  • Определить день и время для регулярных встреч (по сути — задать длительность итераций). В нашем случае мы сошлись на итерациях длиной в 1 неделю. Эти встречи не передвигались и не отменялись.
  • Построить встречи в формате: демонстрация результатов работы + обмен обратной связью + планирование задач на новую итерацию с назначением ответственных.

    Мы не использовали специальную терминологию, хотя сами встречи по смыслу очень напоминают связку из scrum: sprint review + retro + sprint planning. По факту это была одна цельная встреча с заранее определенной повесткой, которая закрывала предыдущую итерацию и начинала новую.

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

От “Мы и они” до “Мы”

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

Важно было для начала разобраться между собой: как именно мы хотим выстраивать работу в коллективе. Поначалу один из будущих руководителей высказал желание держать с сотрудниками дистанцию: “Вы как знаете, но я бы хотел, чтобы ко мне они обращались по имени-отчеству”. Помню, как на этом моменте я нервно поперхнулась. Может, потому что нам всем было по 25, а может потому что я не знала отчества ни одного из моих предыдущих руководителей. “А для работы мы им подготовим инструкции на все случаи жизни”. Всё это капитально не вязалось с моим представлением о том, как нужно работать.

Мне хотелось сделать маленькую, но сильную команду, где отношения строились бы на взаимном уважении и доверии. Чтобы сотрудники чувствовали себя комфортно и уверенно. Так что на этом этапе нам пришлось проработать и этот момент: перейти от подхода “я начальник, ты дурак” к подходу “мы будем работать в одной команде”. Это было не так просто, мы пришли к этому через долгие беседы и поиск корня проблем. Как мне сейчас кажется, взгляды каждого из нас на то, как нужно строить отношения с сотрудниками, были определены той корпоративной культурой, в которой мы до сих пор варились. Кто-то был воспитан стартапами, а кто-то работой на заводах. Но переносить культуру завода в котокафе не совсем верно.

Важно помнить, что и у наших потенциальных сотрудников за спиной был какой-то опыт работы. И если они уже успели привыкнуть к “ты начальник, я дурак”, то и для них работа в команде была бы стрессовой. Поэтому при выборе сотрудников мы ориентировались на схожесть взглядов и идей, умение общаться и подход к решению нестандартных задач. Помимо обычных вопросов мы предлагали соискателям проблемные ситуации и оценивали предложенные ими решения. На что мы не обращали внимания: дипломы, медали и прочие регалии. В итоге мы выбрали правильную стратегию и взяли в команду действительно крутых ребят, с которыми легко было строить дальнейшую работу.

“Зачем твой аджайл сотрудникам?”

“Ок, Катя, список задач и общие встречи помогли нам при открытии. Мы каждый день выполняли нестандартные задачи и нам нужно было обмениваться результатами. Но при чем тут сотрудники? У них же будет однотипная работа, один раз научили — и в бой.” — об этом меня спросили, когда мы уже открылись, и встал вопрос о том, как строить дальнейшую работу.

И я не стала спорить. Подумала: “А что, в этом есть логика. Пусть все идет своим чередом.” Тем более, первые недели мы все вместе проводили “в полях”, бок о бок с сотрудниками: отлаживали процессы, обменивались опытом, обучались сами и обучали их, собирали обратную связь.

На самом деле, всё заработало весьма хорошо. Сотрудники мотивированы, не халтурят, коты и гости довольны, любые вопросы решаются оперативно — идеальная картинка. И всё было прекрасно. До того момента, пока мы не уехали в отпуск спустя месяц после открытия. Две недели мы работали только в формате ответов на срочные вопросы в рабочем чате. И когда мы вернулись, отдохнувшие и счастливые, в котокафе нас встретили подавленные и растерянные сотрудники. “Не бросайте нас больше, пожалуйста” — жалобно попросили они.

Берём лучшие практики и применяем в работе

Так мы вышли на первую ретроспективу команды. Не искусственно ввели новую встречу, а лишь когда поняли, что это необходимо.

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

Позже к ретроспективам добавились ежедневные утренние мини-отчёты в рабочем чате. Обычно они были в свободной форме и содержали следующую информацию: планы на сегодня; столкнулся с такими-то проблемами, применил такое-то решение, сработало/не сработало; нуждаюсь в такой-то помощи (срочно/не срочно). Пусть вас не пугает слово “отчёт”, на самом деле эта информация была важна и нам, и сотрудникам в равной степени. Для нас это возможность держать руку на пульсе, для сотрудников — возможность подсветить проблемы. Этот формат чем-то походил на daily scrum, хотя и был адаптирован под нашу специфику.

Итак, на ежедневной основе мы решали текущие вопросы, раз в 2 недели рассматривали идеи по улучшению процессов работы. Почему мы не добавляли, например, планирования и обзоры спринтов? Да потому что в них не было бы никакого смысла. Мы взяли только те практики, которые были реально применимы и полезны в нашем случае. И да, никто из команды не использовал слова “agile” и “scrum” в рабочем процессе, потому что:

  • Применение отдельных практик скрама не даёт вам право называть свой рабочий процесс скрамом.
  • Название не важно, важна цель. А суть всех наших встреч сводилась к процессу непрерывного улучшения. И об этой цели все знали.

Что было дальше?

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

В связи с переездом мы были вынуждены продать котокафе. Сейчас у него другие хозяева, дело живёт, котики продолжают радовать гостей — и это по-настоящему здорово.

Вот только сотрудники ушли через месяц после смены руководства. Говорят, что после нашего ухода работать “по-другому” уже не хочется. А мы и сейчас продолжаем поддерживать с ними связь. Потому что команда — это вторая семья.

А в каких случаях не стоит применять Scrum читайте в другой моей статье «Scrum вам не поможет. Разбираемся, почему».

ссылка на оригинал статьи https://habr.com/ru/company/icl_services/blog/486382/

Как мигрировать с mocha на jest в 14 простых шагов — и зачем

Уже давно я заглядывался на фреймворк для тестирования jest, в котором есть огромное количество всяких вкусных фишек, одна из которых — многопоточное выполнение тестов. При условии того, что у меня был проект на 5000 юнит тестов, миграция обещала быть крайне полезной. Далее я расскажу 14 простых шагов, за которых мне удалось мигрировать — пусть и с некоторыми оговорками — и что мы в результате получили. Спойлер — всё получилось очень круто.

Исходные условия

Дано — монолит с огромным количеством кода, о котором я рассказывал ранее, и который мы продолжаем растаскивать. Давайте не холиварить на тему монолитов в этом посте — всё что я хотел и мог сказать — уже было сказано ранее. Понятно, что проект должен быть разбит, и тогда самой проблемы запуска 5000 юнит тестов не возникнет — но не всё получается сделать сразу.

Для юнитов используется mocha, chai, sinon, rewire, nock, nyc — вот такая вот сборная солянка, которая полностью покрывает наши потребности. В юнитах есть несколько известных, но сложно диагностируемых и исправляемых проблем:

1) Местами используется глобальный sandbox, оставшихся с тех времён, когда ещё был sinon.sandbox.
2) Из-за глобального сендбокса часть юнитов завязаны друг на друга — они используют чужие моки. Это очевидно плохо, но такие случаи сложно выявлять и исправлять.
3) Местами используется глобальная подмена таймеров через sinon.useFakeTimers. И иногда тесты тоже завязываются на таймеры друг друга. Или последующий тест сбрасывает изменения таймеров от предыдущего.
4) Ну и наконец прохождение юнитов занимает порядка 13 секунд. Что в целом терпимо, но некоторые разработчики жалуются на то, что время препуш хука с тестами каждый раз сокращает время их рабочего дня на эти самые 13 секунд.

Почему мультитрединг

Как я уже написал чуть выше, Jest мне в первую очередь был интересен тем, что умеет запускать тесты в несколько потоков. Так как это бекенд тесты безо всякого там puppeteer и прочих внешних компонентов, то юниты фактически не имеют в себе никаких асинхронных операций (не путать с асинхроными функциями) — и что запускай их параллельно что последовательно — время исполнения не изменится, только забьёшь ивент луп и оперативку. Так что в данном случае мультитрединг — фактически единственное и оптимальное средство оптимизации. Было понятно, что возникнут накладные расходы на инициализацию — но было непонятно, насколько они будут велики.

Оставь надежду всяк сюда входящий

Сначала я пробовал использовать какие-то имеющиеся решения для запуска mocha в многопоточном режиме — но таких решений было полторы штуки, и они вываливались с такими стрёмными ошибками, что даже дебажить это не было никакого желания. Например, я точно смотрел mocha-parallel-tests от Дмитрия Сорина, бывшего сотрудника Яндекса. Падало вдребезги — хотя возможно, что проблема была скорее в проекте, а не в раннере. Надеюсь, у Дмитрия всё получится.

Так же я наудачу пробовал просто взять и смигрировать тесты при помощи jest-codemods — но увы, всё тоже падало, судя по всему — ломалось на sinon — а править 5000 юнитов не было ни времени ни желания. Хотелось более простого решения.

Таки миграция!

1. Установка

В очередной рад с тоскливой завистью просматривая список фишек из awesome-jest, я вдруг заметил jest-runner-mocha. "Это может сработать!" — подумал я, и решил так же быстро попробовать, взлетит ли джест с мокой в виде тест раннера.

Ну что же.

npm install --save-dev jest jest-runner-mocha

Пишем простой конфиг запуска jest-test.config.js

module.exports = {     runner: 'jest-runner-mocha',     testRegex: 'tests/.*test_.*\\.js$',     maxWorkers: 3 };

и запускаем тесты

node --use_strict ./node_modules/.bin/jest --no-cache --config jest-test.config.js

Да, я большой фанат глобального стрикт мода, поэтому запуск выглядит именно так.
Кстати, кеш — тоже очень крутая фишка jest. А отключил я его для отладки на всякий случай — включить его потом всегда можно.
И — внезапный результат — мгновенно прошло порядка 90% тестов, что было прямо феерически хорошим результатом! И было ощущение, что прошли они чуть быстрее — точно понять было нельзя, так как некоторые не проходившие тесты тупо зависали.

2. Выбор репортера

Для джеста, как и для моки, есть богатый выбор репортеров. Тот, который по умолчанию, тоже клёвый — при запуске ты чувствуешь себя не разработчиком, а пилотом космического корабля. Это очень круто, но при наличии сотен наборов быстрых тестов ты всё равно не видишь там ничего полезного, а история терминала просто засоряется. Так что я выбрал простой jest-dot-reporter — он рисует progress bar и говорит количество прошедших, упавших и выключенных тестов — ничего лишнего.

Выбор репортера делается через CLI опции или через config:

    reporters: ['jest-dot-reporter'],

Кстати, его в списке awesome-jest почему-то не было. Теперь будет — пулл реквест я добавил.

3. Использование актуальной версии mocha

Есть у меня плохая привычка — смотреть код пакета, который я использую. И вот залез я в jest-runner-mocha. И обнаружил, что он использует для запуска тестов mocha версии 3.5. Когда как последняя — 7. Мейнтейнер на предложение обновиться говорит, что он хочет поддерживать Node 4. На аргументы, что

  • Node 4 не поддерживает уже ни mocha ни jest, ни даже yarn, который любит ментейнер
  • Уже Node 8 дошла до End Of Life
  • Можно сделать обновление мажорной версии, а пользователи на старой ноде могут продолжать пользоваться прошлой версией
  • Можно сделать mocha в виде peerDependency, и пользователь сам выберет свою версию

внятного ответа получено не было. Ну ладно. Делаем форк. В форке мока теперь в peerDependency — то есть будет использоваться та же версия, что указана в проекте.

4. Реализуем свой clearMocks

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

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

Так что я решил пойти более коротким и грязным путём — добавил в свой форк поддержку опции setupFilesAfterEnv, а в них — поддержку экспорта функции с именем clearMocks и при её нахождении — вызов её перед каждым вызовом набора тестов. Не самое элегантное решение, но навскидку больше ничего не пришло в голову. Кроме того, поддержка clearMocks от jest мне никак не помогла бы — поскольку моки в проекте были от sinon, и jest не мог их сбросить так как ничего не знал про них.

Так что в конфиг джеста добавились опции:

    setupFilesAfterEnv: [ 'lib/clearMocks.js'],     clearMocks: true,

Ну а lib/clearMocks.js представляет собой

// const jest = require('jest-mock'); const sinon = require('sinon');  module.exports = {     clearMocks: () => {         // jest.clearAllMocks(); возможно, сможет скидывать нативные моки джеста, надо проверять         sinon.sandbox.restore();     } };

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

5. Правим оставшиеся тесты

Конечно, остались какие-то кривые единичные случаи. Например, нестабильно воспроизводимая проблема, когда при некоей последовательности запуска тестов падали циклические зависимости (вот почему я всегда говорю их не использовать). Ещё были тесты с использованием фальшивых таймеров без их восстановления. Но таких проблем осталось не слишком много, и поправили мы их довольно быстро. Ура — тесты проходят! Думаете, всё? Нет, история продолжается!

6. Сотни ивентов

При запуске тестов я обратил внимание на то, что при запуске тестов на process вешается 500 обработчиков exit. Что-то явно шло не так. В коде раннера я заметил прекрасный кусок

process.on('exit', () => process.exit());

Мало того, что он не имеет никакого смысла, так этот ивент ещё и вешается при прохождении каждого набора тестов… В общем, в моём форке бага исправлена, а пулл реквест в оригинальный репозиторий пока изучают… slowpoke.jpg.

7. Ещё сотни ивентов

После того, как я разобрался с предыдущими багами, теперь я заметил, что на process вешаются сотни обработчиков unCaughtException. На этот раз, проблема оказалась не в раннере, а в самой моке — достаточно было обновиться с 7.0.0 до 7.0.1.

8. Крадём чужие пулл реквесты

Есть у меня привычка отсматривать пулл реквесты в проектах, которые мне интересны — в особенности в форках, которые я поддерживаю. Обнаружил отличный пулл реквест в jest-runner-mocha, который заменял всю ручную работу с воркерами на стандартный флоу в jest и исправлял связанные с этим баги. К сожалению, тут опять потерялась совместимость со священным граалем — четвёртой нодой — поэтому пулл реквест висит не принятый. Ну что же — я влил его в свой форк, а заодно выпилил из него сборку бабелем, совместимость с четвёртой нодой и yarn.

9. Зовём чувака на работу

Переставляя зависимости в основном проекте, внезапно обнаруживаю новый postinstall hook от транзитивной зависимости jest-runner-mocha:

> core-js@3.6.4 postinstall  > node -e "try{require('./postinstall')}catch(e){}"  Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!  The project needs your help! Please consider supporting of core-js on Open Collective or Patreon:  > https://opencollective.com/core-js  > https://www.patreon.com/zloirock   Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)

Не люблю такие вещи, поэтому пошёл смотреть, в чём дело. Выжимку можно прочитать в этом комментарии. Если вкратце — автор пакета c 23,528,407 еженедельных установок, одного из базовых компонентов бабеля — работал над ним бесплатно пять лет, а сейчас у него проблемы, огромный долг, и ему грозит тюрьма. Сообщество бабеля отказалось спонсировать его проект, на пожертвованиях он собрал 57 баксов, и получил тонны негативных комментариев за свой пост инсталл хук. После чего решил оставить этот хук на неопределённое время. Поддерживаю. Последний коммит автора был от 12 января. Надеюсь, с ним всё в порядке. Предложил ему работу в нашей компании. После написания поста ещё закину пожертвовование. Предлагаю вам сделать так же. Кстати, это не имеет значения, но он наш соотечественник.

Что же до пост инсталл хука — проблема с ним решилась сама чуть позже.

10. Переносим линтинг в джест

Не относится напрямую к тестированию, но если ты вдруг начал забивать гвозди молотком вместо ложки — очень сложно остановиться. Линтинг всего репозитория в CI у нас занимал порядка двух минут, и, поскольку он тоже полностью синхронный, мне так же давно хотелось сделать его многопоточным. Как-то давно я уже пробовал esprint — но там всё было не слава богу, и я на это забил.

Но для джеста есть отличный раннер jest-runner-eslint — который просто берёт движок еслинт, запускает его в несколько потоков, и агрегирует результаты. И надо сказать, что тут реально всё взлетело безо всяких плясок с бубнами. Линтинг ускоряется примерно в то количество раз, сколько у тебя воркеров — пока не упрёшься в память.

Надо сказать, что забавно, что всё новое — хорошо забытое старое. По факту Jest просто повторил всё то, что уже было в раннерах вроде grunt или gulp. Просто те раннеры раньше использовались для сборки фронта, а сейчас всё это, включая многопоточное выполнение задач, включил в себя вебпак. Так что Jest переоткрыл заново эту же нишу, сузив её и удобно реализовав…

11. Исправляем CI

Раньше в CI у нас параллельно выполнялся линтинг и юнит тесты, чтобы хоть как-то рационально использовать ядра на арендуемых виртуалках. Это иногда создавало кашу из вывода линтера и юнит тестов, но в целом было терпимо, и точно проходило быстрее, чем последовательно. Теперь можно было вернуться к последовательному исполнению — ведь и юниты и тесты проходили в несколько потоков!

12. Эпическая подстава

В какой-то момент я вдруг заметил, что у меня перестали проходить тесты на форке мока раннера. Какое-то время пришлось потратить на то, чтобы сравнить изменения… А потом я подумал, что видимо не просто так тесты должны были запускаться после установки при помощи yarn, и наверное не просто так там лежал файл yarn.lock. Да. Дело было в лок файле.

Это просто эпическая подстава. Потому что даже если вы используете yarn для управления зависимостями — лок файл от yarn внутри пакета использоваться не будет. Он используется только для верхнего уровня. Что это значит? То что тот код, который отрабатывает в юнит тестах на установленных из лок файла пакетах — будет работать некорректно внутри проекта.

Что делать? В качестве быстрого фикса, я использовал утилиту для конвертации лок файла от yarn в package-lock.json, после чего переименовал его в npm-shrinkwrap.json (их формат идентичен). Если вы не знали — лок файл игнорируется при установке, а вот shrinkwrap нет — и транзитивные зависимости вашей зависимости ставятся ровно тех версий, которые указаны в shrinkwrap. Конечно, это плохая практика, и в результате количество зависимостей в основном проекте чудовищно раздулось. Зато я уверен, что всё работает точно так, как должно. А эти зависимости всё равно уходят при сборке артефакта на npm prune --production.

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

13. Форматирование ошибок

И только на этом моменте я заметил, что ошибки выдаются в кривом формате — вместо красивых ассертов всё вываливается массивом текста со слетевшим форматированием и цветами. Грешил на многое, но проблема оказалась опять же в раннере. Возможно, более ранние версии jest умели воспринимать ошибки массивом, но сейчас там ожидается строка. Фикс есть в форке, в оригинальном репозитории пока без комментариев.

14. Описание миграции

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

Результат

Так что в итоге мы получили после миграции?

  1. Скорость тестов и линтинга локально и в CI увеличилась примерно в 3 раза на 3х потоках. Наверное, можно и больше, пока просто мало играл с количеством воркеров.
  2. Ушли завязки тестов друг на друга. То что происходит в тесте — остаётся в тесте. Более того, разбивка тестов на несколько процессов даёт возможность выявления дополнительных не очевидных ошибок.
  3. Можно использовать встроенные возможности jest вместо всего того зоопарка решений, который развёлся за эти годы.

ToDo

Что ещё осталось сделать:

  1. Пока я не смотрел, как работает проверка покрытия. Возможно, там будут проблемы. Но это не очень критично, так как mocha никуда не уходит, и можно спокойно считать покрытие как раньше.
  2. Можно дать разработчикам возможность писать тесты полностью на jest — просто сделать другой префикс для тестовых файлов. Но это породит некоторый зоопарк и проблемы с тем что придётся сливать между собой покрытие от разных раннеров — так что надо ещё подумать, стоит ли.
  3. В пакете раннера ещё явно остались проблемы — в том числе с юнит тестами и зависимостями. Возможно, это ещё выстрелит в ногу. Или нет. Всё же, выборка в 5000 юнитов неплохо покрывает самые разные кейсы.
  4. Возможно, стоит теперь ещё раз пройтись по коду при помощи jest-codemods и посмотреть, что можно безопасно заменить — например, сменить для начала библиотеку для ассертов.

Да, если что, вот ссылка на мой форк раннера mocha.
Поставить его можно командой

npm install --save-dev @kernel-panic/jest-runner-mocha

А в конфиге прописать как

runner: '@kernel-panic/jest-runner-mocha',

ссылка на оригинал статьи https://habr.com/ru/post/486360/

Переписываем генератор паролей

Password Policy

Пароли вне политики

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

Начнем с простых требований к паролю:

  • должен быть произвольной длины
  • должен состоять из любых печатных символов

import string import random from typing import List  def generate_password(length: int) -> str:      """      Generate a password of a given `length`.      """      result: List[str] = []      choices = string.printable # заглавые и строчные буквы, цифры и знаки препинания      while len(result) < length:          symbol = random.choice(string.printable)          result.append(symbol)      return "".join(result)

Пробуем:

>>> generate_password(8) ... "1{k]/2)h" >>> generate_password(13) ... "9ar|&:a+U]Il$"

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

Внезапная политика

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

  • должен быть не меньше 8 символов
  • должен содержать как минимум одну заглавную букву
  • должен содержать как минимум одну строчную букву
  • должен содержать как минимум одну цифру
  • должен содержать как минимум один спец-символ (!&? и прочие)
  • а еще он не должен содержать некоторые символы, чтобы не поломать bash-скрипты

Наш код выше для этого не годится, так что придется дорабатывать. И для решения этого я видел целый ворох разных подходов:

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

Новый подход

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

import string import random from typing import List  def generate_random_string(length: int, *choices: str) -> str:     """     Generate a string of a given `length`.      The result has at least one symbol from each of `choices` if `length` allows.      Arguments:         length -- Result string length.         choices -- Strings with available symbols.     """     if not choices:         # будем использовать только буквы если нам все равно, из каких символов пароль         choices = (string.ascii_letters, )       # создадим строку со всеми доступными символами     all_choices = "".join(choices)     result: List[str] = []     choice_index = 0     while len(result) < length:         # получим по символу из каждого списка, чтобы         # каждый список был использован хотя бы один раз         if choice_index < len(choices):             symbol = random.choice(choices[choice_index])             result.append(symbol)             choice_index += 1             continue          # а после этого добавляем символы из любого списка         symbol = random.choice(all_choices)         result.append(symbol)      # перемешаем наш результат чтобы распределить начальные символы     random.shuffle(result)     return "".join(result)

Так, попробуем:

>>> # генерируем строку из цифр >>> generate_random_string(8, string.digits) ... "59197550" >>> # а тут обязательно должен быть восклицательный знак >>> generate_random_string(8, string.ascii_letters, "!")  ... "vIOWXN!o"

Отлично, пришло время собственно сгенерировать пароль, отвечающий всем нашим требованиям.

def generate_mydb_password(length: int) -> str:     """     Generate a random password for MyDB of a given `length`.      The result has at least:     - one uppercase letter     - one lowercase letter     - one digit     - one special character      Raises:         ValueError -- If `length` is lesser than 8.     """     if length < 8:         raise ValueError("Password length should be at least 8")      return generate_random_string(         length,         string.ascii_uppercase, # в пароле должны быть заглавные буквы         string.ascii_lowercase, # и строчные         string.digits, # и цифры         "!&?", # и спец-символы, добавьте нужных по вкусу     )

Осталось только проверить:

>>> generate_mydb_password(8) ... "P?P1&7zL" >>> generate_mydb_password(13) ... "tR!QslK!Sl7EO" >>> generate_mydb_password(2) ... ValueError: Password length should be at least 8

Итого

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

Спасибо за внимание!

ссылка на оригинал статьи https://habr.com/ru/post/486414/

.NET Core + RabbitMQ

При разработке микросервисов возникает вопрос о предоставлении информации от одного сервиса другим сервисам по мере возникновения каких-либо событий. При этом важно иметь систему без привязки отправителя к получателям. В этом случае используется паттерн Publisher-Subscriber.

На рынке существует множество продуктов для обмена сообщениями, которые поддерживают паттерн Publisher-Subscriber, например Azure Service Bus, RabbitMQ или Apache Kafka.

Недавно мною были опубликованы две NuGet библиотеки для быстрой и удобной настройки event-based communication с использованием Azure Service Bus и RabbitMQ. В этой короткой how-to статье описаны шаги для использования последней.

Исходные

Publisher — .NET Core приложение, которое выступает в роли отправителя.
Subscriber — .NET Core приложение, которое выступает в роли получателя.

How-To

1. В publisher и subscriber приложениях установите две NuGet библиотеки.

PM> Install-Package Autofac.Extensions.DependencyInjection PM> Install-Package EventBus.RabbitMQ.Standard 

2. В publisher и subscriber приложениях добавьте конфигурацию в appsettings.json.

{   "RabbitMq": {     "BrokerName": "test_broker",     "AutofacScopeName": "test_autofac",     "QueueName": "test_queue",     "RetryCount": "5",     "VirtualHost": "your_virtual_host",     "Username": "your_username",     "Password": "your_password",     "Host": "your_host",     "DispatchConsumersAsync": true   } } 

Эти настройки можно получить на CoudAMQP. Альтернативно можно использовать RabbitMQ локально (Docker image).

3. В publisher и subscriber приложениях создайте класс для события, которое будет публиковаться и, в дальнейшем, обрабатываться.

public class ItemCreatedIntegrationEvent : IntegrationEvent {     public string Title { get; set; }     public string Description { get; set; }      public ItemCreatedIntegrationEvent(string title, string description)     {         Title = title;         Description = description;     } } 

4. В subscriber приложении создайте класс для обработчика события ItemCreatedIntegrationEvent.

public class ItemCreatedIntegrationEventHandler : IIntegrationEventHandler<ItemCreatedIntegrationEvent> {     public ItemCreatedIntegrationEventHandler()     {     }      public async Task Handle(ItemCreatedIntegrationEvent @event)     {         //Handle the ItemCreatedIntegrationEvent event here.     } } 

5. В publisher и subscriber приложениях обновите Program.cs, добавив одну строчку.

public class Program {     public static void Main(string[] args)     {         CreateHostBuilder(args).Build().Run();     }      public static IHostBuilder CreateHostBuilder(string[] args) =>         Host.CreateDefaultBuilder(args)             .UseServiceProviderFactory(new AutofacServiceProviderFactory()) //Add this line.             .ConfigureWebHostDefaults(webBuilder =>             {                 webBuilder.UseStartup<Startup>();             }); } 

6. В publisher приложении обновите метод ConfigureServices в Startup.cs.

public class Startup {     public void ConfigureServices(IServiceCollection services)     {         ...          var rabbitMqOptions = Configuration.GetSection("RabbitMq").Get<RabbitMqOptions>();          services.AddRabbitMqConnection(rabbitMqOptions);         services.AddRabbitMqRegistration(rabbitMqOptions);          ...     } } 

7. В subscriber приложении создайте расширение EventBusExtension.

public static class EventBusExtension {     public static IEnumerable<IIntegrationEventHandler> GetHandlers()     {         return new List<IIntegrationEventHandler>         {             new ItemCreatedIntegrationEventHandler()         };     }      public static IApplicationBuilder SubscribeToEvents(this IApplicationBuilder app)     {         var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();          eventBus.Subscribe<ItemCreatedIntegrationEvent, ItemCreatedIntegrationEventHandler>();          return app;     } } 

8. В subscriber приложении обновите методы ConfigureServices и Configure в Startup.cs.

public class Startup {     public void ConfigureServices(IServiceCollection services)     {         ...          var rabbitMqOptions = Configuration.GetSection("RabbitMq").Get<RabbitMqOptions>();          services.AddRabbitMqConnection(rabbitMqOptions);         services.AddRabbitMqRegistration(rabbitMqOptions);         services.AddEventBusHandling(EventBusExtension.GetHandlers());          ...     }      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)     {         ...          app.UseAuthorization();          ...     } } 

9. Для демонстрации и простоты можно создать контроллер в publisher приложении с методом в котором происходит публикация события.

public class ItemController : ControllerBase {     private readonly IEventBus _eventBus;      public ItemController(IEventBus eventBus)     {         _eventBus = eventBus;     }      [HttpPost]     public IActionResult Publish()     {         var message = new ItemCreatedIntegrationEvent("Item title", "Item description");          _eventBus.Publish(message);          return Ok();     } } 

10. Теперь можно опубликовать ItemCreatedIntegrationEvent. Запустите оба приложения, вызовите POST метод Publish в subscriber и обработайте событие в subscriber.

Ссылки

ссылка на оригинал статьи https://habr.com/ru/post/486416/