Перейти к основному содержимому

Интеграционные тесты на дедлоки и одновременные запросы

· 4 мин. чтения

Я в последнее время всё больше люблю писать интеграционные (API) тесты — запускаю половину приложения, но не привязан к UI. Это золотая середина между очень медленными end-to-end тестами и очень быстрыми unit-тестами. Рассмотрим особый случай таких тестов, которые используют заготовленные данные под каждый тест. 

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

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

Кто такой социопат

· 15 мин. чтения

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

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

Тестирование файловой системы с vfsStream

· 3 мин. чтения

Если вы заботитесь о качестве своего проекта и кода, то пишете unit-тесты. Но с ними всегда есть «особые случаи». Один из них - работа с файловой системой и ресурсами. Решение «в лоб» - параллельно создавать папку/дерево специально для тестов и надеяться что они не прыгнут на настоящие пути и ничего не удалят.

Более правильный подход - использование in-memory виртуальной файловой системы, vfs. А поскольку ресурсы это по сути потоки, то и название для этой мок-библиотеки - vfsStream. Ставим..

composer install mikey179/vfsStream

Тестирование метода где есть new instance

· 4 мин. чтения

Я когда-то писал про то что в phpunit небыло возможности нормально протестировать внутренние методы класса и приходится обращаться к runkit. Незнаю, была ли это моя недоработка, либо с версии 3.8 прошло уже много времени, но в 4.5 эта возможность есть! Моки в 4.5 стали удобней — они умеют перезаписывать как весь класс, так и его части.

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

Связывание тестов через @ticket аннотации с Jira

· 5 мин. чтения

Jira от Atlassian — самый современный трекер задач и багов позволяющий гибко настроить workflow организации. Но если вы не доросли, не хотите использовать Bamboo, а скажем используете PHPCI для автоматического тестирования, то вам возможно будет полезно видеть результаты прогона тестов сгруппированных по фичам.

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

По-моему эта методика достаточно полезна для повышения прозрачности покрытия фич. Если разработчик пишет новую фичу, то это не значит что он сразу же покроет код тестами. И даже если код на 100% покрыт юнит-тестами, это не значит что ошибок нет. Надо писать интеграционные тесты. А интеграционные тесты как правило не генерируют покрытия. Поэтому видеть - написаны ли какие-то тесты для фичи полезны для уверенности. Это справедливо и если вы программист и не следуете методике TDD "test first" и если вы менеджер и для вас покрытие тестами недостаточно прозрачно, не гранулярно.

Кайдзен

· 5 мин. чтения

суббота, 21 февраля 2015 г. в 21:02:54

Кайдзен — по-японски означает «изменение к лучшему», это нечто типа трезвения и осознанности, полезная практика постоянного самосовершенствования (constant improvement) ради всеобщего блага. Популяризируется она благодаря тому что это фундамент, используемый на фабриках корпорации Toyota для эффективного производства.

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

Визуализация степени подгрузки js-файлов

· 2 мин. чтения

Современные приложения всё больше начинают напоминать полноценное desktop решение, где в итоге запускается build-процесс с помощью grunt и мы получаем один кешируемый js файл. Теперь встаёт вопрос, как бы его загрузить так что-бы показать % подгрузки?

Есть два отдельных скрипта — $script, который инжектит новые script-элементы с обратной связью и progress.js, который симпатично показывает степень загрузки. Проблема в том что оба они уже сами по себе тяжёлые, в итоговый загружаемый билд-файл их нельзя вставить, а внедрять в index.html слишком бы его раздуло.

К тому же $script не поддерживает степень загрузки, а progress.js не использует % от ширины страницы. Поэтому я написал более простенький вариант.

$script = function (path, callback, sizeInChars) {
var head = document.getElementsByTagName('head')[0];
var body = document.getElementsByTagName('body')[0];
var div = document.createElement('div');
div.setAttribute('style', 'background-color:#3498db; width:0px;height:3px;transition: width 0.5s linear;');
body.insertBefore(div, body.firstChild);
var src = path + (path.indexOf('?') === -1 ? '?' : '&');
var oReq = new XMLHttpRequest();
oReq.addEventListener("progress", function (e) {
div.style.width = (100 * ( e.lengthComputable ? (e.loaded / e.total) : (e.loaded / sizeInChars))) + '%';
}, false);
oReq.onreadystatechange = function (e) {
if (oReq.readyState != 4) return;
var el = document.createElement('script');
el.text = oReq.responseText;
head.insertBefore(el, head.lastChild);
callback();
window.setTimeout(function () {
div.remove();
}, 500);
};
oReq.open('GET', src, true);
oReq.send(null);
}

Итого — js файлы подгружаются как текст через ajax, внедряются (без eval), вызывается callback, при этом показывается div отражающий степень загрузки. Единственная проблема — вы должны знать размер загружаемого файла, что в случае nginx + gzip не приходит с заголовками. Поэтому приходится использовать например так..

$script('/js/app.js', function () {
angular.bootstrap(document, ['myApp']);
}, 4879435);

Как параллельно запустить процессы в sh скрипте

· 2 мин. чтения

Порой надо написать какой-то установочный скрипт, который требует одновременный запуск нескольких задач, из которых некоторые достаточно долгие — сервер, билд проекта. Это не то же самое что последовательный запуск с «&&» или «;» разделителями. Shell-скрипты могут и это с помощью комманды trap.

Комманда работает на прерывании процесса (второй параметр). В данном случае это EXIT и TERM. Основная комманда - убиение запущенных этим терминалом в фоне процессов (jobs -p). Сами они перечисляются дальше с группировкой фигурными скобками.

Первый trap таким образом вызывает последующие вызовы. Заметьте что второй вызов с инициализацией базы mongo стоит с задержкой в 3 секунды, что-бы сервер успел подняться (который уже будет бежать бесконечно)

echo 'db.createCollection("myCollection");' > mongoInit.js

trap 'echo Starting MongoDB with preinstalled collections; kill $(jobs -p)' EXIT
{ trap '/usr/local/bin/mongod --dbpath myMongoDBStorage' TERM; sleep 5 & wait; } &
{ trap 'sleep 3 && mongo myProjectDB mongoInit.js' TERM; sleep 5 & wait; } &
sleep 1

 

На практике получается впрочем так, что комманды бегут бесконечно, т.е. mongod запускается, вы нажимаете Ctrl+C, а он всё ещё работает.. jobs ничего не показывает, но если возникает соединение - контекст возвращается. Приходится использовать killall mongod

Вдохновлено stackoverflow. См. теорию - Signals & Traps 

upd. как оказалось, более простой и эффективный способ - использовать одинарный &, который пушит комманды в фоновый режим и запускает дальше следующие

/usr/local/bin/mongod --dbpath myMongoDBStorage & sleep 3 && mongo myProjectDB mongoInit.js

 

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

Про infinite scrolling

· 4 мин. чтения

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

Технически, данные на frontend хранятся в какой-то коллекции/сервисе. То что показывается пользователю — подмножество этих данных, т.е. может быть отфильтрованным, отсортированным и изменяемым на лету. Таких подмножеств в приложении может быть несколько, в зависимости от отображения (view). 

В одном месте у вас товары одного бренда, в другом месте у вас заказанные товары, в третьем - товары конкретного пользователя.

Что значит «ещё»?

Это значит, что вопрос подгрузки новых данных - нетривиальная задача.

Сначала я подумал что лучше всего запрашивать данные по времени. У каждого ряда данных будет timestamp, как у твиттера, соответсвенно запрос будет высылаться "подгрузи 20 рядов данных, старше X", а время будет браться у последнего ряда. 

Но тут возникает несколько проблем. 

  1. Что если у данных одно и то же время? Если на backend добавить вторую сортировку по ID, и передавать ID последнего ряда вместе со временем, то это всё-равно не спасает положения, т.к. значительно усложняется вычисление OFFSET в SQL запросе.
  2. Если запрос зависит от последнего показанного ряда, то получается что запрос формируется на основе подмножества, т.е. зависит от view. Это плохо, т.к. завязывает модель, контроллер и view в единое целое. Альтернатива - дублировать фильтрацию которая происходит для view в модели.
  3. Что если данные обновились со времени последнего запроса? Надо ли их добавлять в начало списка? 
  4. Что делать если данные отсортированы не по времени, а по алфавиту? И они вдруг поменялись? 

Вобщем приходишь к тому, что для универсального решения, надо по-прежнему работать с OFFSET и ORDER BY, как то было с постраничной навигацией. При этом offset высылается в зависимости от нужды view и он равен размеру подмножества.На случай обновившихся данных, я использую socket.io, который оповещает что надо обновить. Добавившиеся данные я сразу внедряю в список, а update происходит автоматически

Когда загружать?

ngInfiniteScroll - простенькая ангулярная директива для ангуляра, которая триггерит нашу функцию подгрузки если вы достаточно проскроллили вниз (scrollTop() близок к высоте элемента). Проблемы в ней две - она привязана ко всему окну (window). Обычно же у нас может быть несколько контейнеров на странице где надо подгружать данные. Есть и более простой пример

И вторая проблема следующая из неё - если вы запросили слишком мало данных за запрос (LIMIT 5), и/или их высота динамическая и меньше чем высота контейнера, то ваш scroll никогда не появится и не запустит постоянное дополнение списка. Пришлось это поисправлять, каждый раз расчитывая высоту содержания контейнера.

Тут главное не заDDOSить сервер из-за неправильного расчёта высоты элементов и рекурсии. По-хорошему вызов подгрузки надо начинать до достижения нижнего края.

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

Когда использовать не стоит

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

Во-вторых, бесконечность дезориентирует и лишает контроля. Вы не знаете сколько вы уже проскроллили, дажа примерно глядя на размер бегунка, не можете понять сколько осталось до конца. Аналогично, вы не можете перепрыгнуть в "значительно более далёкие" версии данных.

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

Само собой, если у вас SPA, то могут быть проблемы с индексацией поисковиками. Гугл предлагает гибридный вариант, где данные таки будут доступны и по страницам тоже.

Наконец, если у вас что-то случилось с ajax-запросом и нет хорошей обработки ошибок, бесконечный скролл ломает глубину просмотра. А если сервер достаточно нестабильный, то риск что пользователь никогда не сможет добраться до нужных данных увеличивается с каждым запросом. Например если вероятность ответа сервера HTTP 500 оценивается в 5%, то после 15ти последовательных запросов вероятность что лента будет сломана уже (100 - 100 × 0.9515) = 55%.

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

По теме:

Interstellar

· 6 мин. чтения

Interstellar, новый фильм Кристофера Нолана эпичен, масштабен, грандиозен и со своими чёрными дырами. В сущности фильм затрагивает три темы — любовь и эгоизм, судьбу человечества, время и смерть. Хорошо балансирует между космической одиссеей и опытом-чувствами, которые становятся большей ценностью для нашего общества. По качеству:

  • Хороший набор актёров
  • Отличный визуальный ряд
  • Классная, атмосферная музыка Ханса Циммера
  • Закрученный сюжет, хоть и со своими неровностями
  • Продуманная научная тема

Обязательно посмотрите, а теперь честность на 100% и сплошь спойлеры..

UserScripts

· 3 мин. чтения

Часто сталкиваюсь с желанием поменять что-то на сайтах, добавив свой функционал, сделав их удобней или автоматизировав многие вещи. Во многих случаях это становится хорошим лайфхаком. К счастью для браузеров есть расширения, добавляющие возможность запуска user-скриптов. Для firefox это greasemonkey, а для chrome - tampermonkey.

Движки "обезьянок" позволяют подключить внешние библиотеки (например jquery) и напрямую манипулировать страницей. Это даёт широкий простор по изменению layoutа и взаимной интеграции сайтов. Например

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

Lucy

· 1 мин. чтения

Посмотрел вчера Lucy. Средне. Хорошие спец-эффекты, вставки как из Baraka под псевдо-философию, что-бы дать видимость глубины мысли, там где её нет. Дальше спойлеры.

Фильм об обожении, но как и большинство последних голливудских фильмов затрагивающих внутреннее развитие и трансцендентность, он слишком материалистичен. Главная героиня с одной стороны становится сверх-умна, теряет чисто человеческие качества ограниченности сознания (страх, боль, страдание), но при этом не отказывается от насилия и поглощения всего наркотика. Полное раскрытие разума даёт способности по управлению и пониманию всего вокруге, но не ведёт ни к чему продуктивному.

Интеграционное тестирование веб-приложения на инъекции

· 6 мин. чтения

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

  • unit-тесты бэкэнда — в основном покрываются модели, генерируется покрытие — получаете необходимость изолировать модели (заодно single responsibility principle выполняется)
  • unit-тесты frontend — карма + phantomjs прокрутят все ваши angular-сервисы и backbone-модели — тоже приходится изолировать код
  • e2e (сценарные, системные) тесты — наверняка основанный на selenium (protractor, selenide). Медленно тестируется функционал работающей системы из UI — приходится задумываться о том что пользователь вообще делает (use cases)
  • db/entity тесты миграций — "с нуля" запускают изменения в БД и когда всё готово - сравнивают с entity/record классами для синхронизации кода с БД (так находятся лишние свойства и недостающие )

Protractor

· 5 мин. чтения

Protractor — движок для запуска системных (end-to-end, браузерных) тестов. Внутри он использует selenium с драйверами для браузера (chromedriver), а сами тесты пишутся с синтаксисом jasmine. Про него и карму я уже писал, впрочем mocha и cucumber тоже поддерживаются.

Из особенностей - protractor имеет интеграцию с angular (находит модели и repeat-директивы) - отсюда и название слов (angle - угол, protractor - транспортир, т.е. угловая линейка), но может тестировать и любые другие сайты. Из недостатков - 

Юнит-тестирование AngularJS с Karma и Jasmine

· 3 мин. чтения

Я когда-то писал про тестирование javascriptа с помощью jstestdriver , поскольку тот был первым интегрированным в PHPStorm, но инструменты развиваются и теперь для ангуляра есть простор для проверки качества кода.

Karma это консольный инструмент для запуска тестов согласно конфиг файлу. Тесты гоняются в разных браузерах, умеет следить за изменениями исходников и генерировать покрытие кода. Есть ещё protractor - близкий по смыслу но для системных тестов.

Jasmine это уже библиотека для тестирования, аналог Mocha.

Недостатки AngularJS

· 3 мин. чтения

Я уже несколько месяцев работаю с AngularJS и после Backbone/jQuery впечатления неоднородные

Framework

Рамки (frames) всегда ограничивают. С Backbone и jQuery можно было писать код, который хочешь, где главное что-бы он работал. С Angular надо глубоко влазить в дебри настроек что-бы всё было по правилам. Это хорошо что приложение следует правилам. Плохо что документации слишком много. Почитайте описания ui-router или binding у директив!

Программирование декларативное

Код связывающий DOM с логикой переместился полностью в html. Селекторы в стиле jquery практически не нужны. Это серьёзно ограничивает используемость фреймворка с существующими скриптами и работающими системами. Попытка соединить два мира и использовать из ng-контроллера каких-то jquery событий будет выглядеть как хак.

Все события вешаются на самом элементе. Это в чём-то хорошо (меньше кода в js), но с другой стороны это распыляет видимость всего приложения и сильней привязывает html к логике. Например теперь проще делать показывание элементов с ng-show, но когда у вас на элементе висит ng-class, ng-src, ng-repeat, ng-click и ещё с десяток директив, в том числе самописных, то становится уже сложно не то что читать html, но и понять состояния к которым элемент может прийти или порядок вызова событий.

Проблема с bindингом моделей

Проблема обновления DOM никуда не пропала. Модели это обычные объекты в которых спрятан параметр $$hashKey, по которому и привязывается двустороннее обновление DOM. Проблема в том что своей логикой вы можете модели поломать так, что они лишатся этого ключа и тогда DOM останется жить своей жизнью, а вы не заметите этого. Такой баг отловить сложней всего.

Чаще всего потеря случается из-за того что у вас есть локальный кэш данных в памяти (модель), который асинхронно обновляется через $http, но вы используете присваивание для обновления.

Например - в контроллере вы привязали view к кешированным данным из сервиса

$scope.user = UserService.getUser();
UserService.updateCache();

Если ваш сервис будет просто перезаписывать внутреннюю переменную, то это не обновит view.

UserService = ['$http',
function($http){
var _localUserCache;
return{
updateCache:function(){
$http.get('user').then(function(response){
_localUserCache = response.user;
});
},
getUser: function(){
return _localUserCache;
}
}
}
]

Это из-за того, что в javascript данные передаются по ссылке (by reference), но если вы присваиваете новое значение, то старое значение (которое присвоено во view) в памяти то останется, но создадутся и новые данные. Поэтому правильно использовать angular.copy:

angular.copy(response.user, _localUserCache);

Второй частый источник батхёрта с bindом - удаление элементов из коллекций. Их надо делать с помощью array.slice(i), а не с присваиванием undefined или delete ключевым словом

Escaping и кавычки

Escaping в ангуляре встроен - вы не можете из фильтра просто так выплюнуть HTML. Это с точки зрения безопасности хорошо, но с другой стороны - долпонительная проблема. Надо использовать $sce.trustAsHtml(). Но я даже не на это жалуюсь..

Допустим у вас есть переводы как фильтр. Вы хотите в переводы вставить ссылку.. то как вы её передадите? 

{
'text_with_link':"<a ng-click='$state.go(\"app.dosomething\")'>click me!</a>",
'text_with_vars:"{{start_tag}}clickme!{{end_tag}}"
}

Замечаете escaping кавычек? А это внешний, чистый JSON. В общем случае escapingа кавычек внутри реального HTML - беда. Т.е. я себе представляю это так.. но на самом деле оно работать не будет:

<div ng-bind-html="'text_with_link' | translate: '{start_tag:\"<a ng-click=\'$state.go(\\"app.dosomething\\")\'>\"}'"></div>

Наконец Angular никак не развивает язык программирования ради языка. Вам не нужны классы, прототипы, иерархии, наследования, публичные-приватные методы. Подключи зависимости, внедри scope и нужные сервисы и пиши контроллер. Это своего рода деградация ради эффективности.

Из плюсов - мне понравились директивы. То что ты можешь определить визуально изолированный элемент как кирпичик даёт удобство для компоновки всяких wysiwyg, комбобоксов, календариков и тп. Понравилась магия с автообновлением view-моделей, с переводом на лету и с dependency-injection компонентов просто по имени сервиса.

Эволюция разума и человечества

· 4 мин. чтения

Посмотрел несколько интересных лекций по эволюции и начал читать книгу "Сумма технологий" С. Лема. 

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

Популярные 404 ошибки

· 1 мин. чтения

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

Атаки

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

  • Wordpress - wp-login.php
  • PHPmyadmin - phpmyadmin/scripts/setup.php

Иконки для браузера

Роботам

  • robots.txt  - запреты на индексацию для поисковиков

  • crossdomain.xml - запрашивается флешем/sliverlight при кроссдоменных запросах

  • sitemap.xml - для поисковиков

  • humans.txt - авторы сайта

Дебаг JS

  • *.map файлы для дебага минимизированных js-библиотек

Ретина - *@2x.png

Для экранов с высокой плотностью пикселей некоторые сайты добавляют дублирование картинок под ios. Обычно это решается @2x суффиксом и retina.js, либо с помощью css/background-image как то описано чуть ниже. Проблема в том что файлы указывать, а иногда они проскальзывают

.icon{
width: 32px;
height: 32px;
background-image: url(icon.png);
}

@media only screen and (-moz-min-device-pixel-ratio: 1.5),
only screen and (-o-min-device-pixel-ratio: 3/2),
only screen and (-webkit-min-device-pixel-ratio: 1.5),
only screen and (min-devicepixel-ratio: 1.5),
only screen and (min-resolution: 1.5dppx) {
.icon{
background-image: url(icon@2x.png);
background-size: 32px 32px;
}
}

Мониторинг 404 ошибок с google analytics

Phonegap приложение

· 5 мин. чтения

Решил тут поиграть с двумя технологиями которые прям bleeding edge нынче в IT - phonegap и angularjs. Первый позволяет абстрагироваться от родных языков для каждой платформы.. а это между прочим

  • Android - Java
  • iOS - Objective-C
  • Blackberry - Java
  • Palm OS - C, C++, Pascal
  • Symbian - C++
  • Windows C#