“5 hours a day” keeps pressure away

A common question raised by customer-side management:

  • How to control software engineers? 
  • How I, as a customer who pays huge bags of cash per hour to this hippies, can really know that they are not cheating me watching youtube half a day?

But even if the question is not asked explicitly, does not mean nobody has doubts alike. 


I worked in various environments. Let me review the most common solutions and their pros and cons.


Don’t track anything!

This sounds like a programmer’s paradise: one spends time to whatever is more productive at the moment. “Trust is everywhere!

But let’s look at a situation when some urgent side tasks are thrown to the engineer. Say, we had a release and discovered some small bugs after it. We need somebody to look at them and state the estimate for fixes. 

Such investigation tasks may take considerable amount of time. But are easily forgot to be mentioned on the stand-ups (or at worst cases one may be even forced not to mention them with a reason “This does not “bring any value to the current sprint”, but let’s leave such dullards away).

Besides, in this case you kind of “hide” from yourself and the team the real efforts spent for a particular tasks. Sometimes one may spend few hours each day to investigate some naughty bug. And having spent 10 hours one creates a task for minor refactoring that would take 2 hours to be done. I think it’s not fair!

Track your every billing hour

Any creative work needs a concentration and the concentration is very fragile.

During the workday anyone may be distracted by some “not-directly-my-work” questions or attend some short group sync meeting. Where to track that time? Should I create a ticket for every activity over 30min I had during the day? And what to do with those below 30min? Track to the current task? Why wouldn’t I track some time for more-than-30-min activities to the current task too?

This may lead to some inflation of minor tasks.

This forces me to lie. And I don’t want to! And I don‘t want to create a ticket for every activity either!

Track 5 to 8 hours makes perfect sense

This does not force an engineer to lie and gives a legal way to omit some really not important details (e.g. 10 minutes watching cat gifs after lunch).

This 30% buffer is enough to minimize controlling pressure and to concentrate on what has been really done.


A customer may ask: Why I pay for 8 hours and get only 5? 

Because this is not really “hours”. This is a productive result of 8 hours workday. Which is pretty good, as for me. 

Besides, those cheating with 5 hours can cheat with any amount of hours at all and without tracking too.

I believe the modern ways of management are about openness and transparency. So why not to give the customer (especially PO) an ability to track not the time but the efforts for particular tasks to identify problems earlierIsn’t it the “agile” is all about? The records could be a good supplementary material for daily stand-ups especially in remote teams.  

Also, do not forget that hours registered per day is not a real metric of performance, progress or anything alike. This is just a documented version of the stand-up meeting with some tracking capabilities. 

If youre afraid this metric might be used as a punishment reason, probably better not to track anything at all.

Originally posted at my blog at http://lazytesterua.blogspot.com/

Paho MQTT кліент, Quality of Service > 0 та max in-flight messages

Протокол MQTT набирає все більшої популярності у зв’язку з поширенням і популяризацією IoT та SmartHome рішень і технологій (наприклад послуги MQTT брокера надає серед інших Amazon).

Робота з протоколом MQTT для Java розробника часто означає використання в якості MQTT клієнта FOSS бібліотеки Eclipse Paho, яка, напрклад, штатно йде з Spring Messaging.

В роботі з Paho при відправці великої кількості повідомлень за короткий проміжок часу може виникати помилка “Too many publishes in progress“, причина якої полягає в наступному: при відправці з Quality of Service більше за 0 (нуль) кожне відправлене MQTT брокеру повідомлення має бути збережене поки брокер не підтвердить отримання, і перевідправлене якщо брокер не підтвердив отримання повідомлення за якийсь період часу. Збережені непідтверджені повідомлення називаються в термінології Paho in-flight, і кількість таких повідомлень обмежена.

Звичайно, найпростішим і гарантованим способом уникнути цієї помилки є використання нульового рівня Quality of Service. Але є і інші варіанти. Continue reading “Paho MQTT кліент, Quality of Service > 0 та max in-flight messages”

Tricky issue on jackson serialization/deserialization

Just few days ago I was implementing small feature on one of our minor projects .  The idea process some data on user interface pass it to backend and send it further to third party service.

So on UI we have code like this

$.post( "/backendService", JSON.stringify(idList); );

Obviously idList contains plain long type id values e.g. 54396 so that on backendService we have following piece of code

@SuppressWarnings("unchecked")
List<Long> idList= mapper.readValue(idListJson, List.class);

And later on I would like include that list as part of other structure

public class ThirdPartyServiceRequest {

public List<Long> idList = new ArrayList<Long>();
..........

so  I did following

ThirdPartyServiceRequest request = new ThirdPartyServiceRequest();
            request.idList.addAll(idList);
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(request);
            executePOSTQuery("ThirdPartyServiceURL", json);
......

So everything pretty clear I wrote unit test where I manually created idList and so on, everything worked like a  charm, but when I started testing the whole solution following line

String json = mapper.writeValueAsString(request);

threw me exception

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: java.lang.String cannot be cast to java.lang.Long (through reference chain: com.pack.ThirdPartyServiceRequest ["idList"]->java.util.ArrayList[0])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:210)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:189)
    at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:216)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContentsUsing(IndexedListSerializer.java:142)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:82)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:73)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:19)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:575)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666)
    at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:156)
    at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:129)
    at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3387)
    at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:2781)
    at com.pack.BackendService(BackendService.java:151)
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
    at com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer.serialize(NumberSerializers.java:175)
    at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContentsUsing(IndexedListSerializer.java:136)
    ... 10 more

 

So what a ClassCastException from String to Long is mentioned if we do exactly opposite and convert List<Long> to String ?

I’m sure many watchful readers already figured out issue. I did a mistake in following line

@SuppressWarnings("unchecked")
List<Long> idList = mapper.readValue(idListJson, List.class);

We cannot do it. If we check json we received from UI we will see following line

JSON.stringify(idList)

returns such json

"["26512","26515"]"

so that when we use jackson to deserialize we should be careful and use proper generic for list

@SuppressWarnings("unchecked")
List<String> idList= mapper.readValue(idListJson, List.class);

or provide proper type

List<Long> idList = mapper.readValue(idListJson, new TypeReference<list<long>>() {})

It’s pure developer faults and it’s quite easy to do such mistake so remember about it

 

UPD.

Thx to comments MVMn the proper fix would change not server side but UI part

 

So before writing to idList you have check if it’s really int and not a String and convert it to int on UI if needed with function parseInt

In my case I receive id as value of checkboxes and obviously it is String not int

Фреймокати чи Не Фреймокати

(п’єса за мотивами реальних подій)

Дійові особи.

Аліса – девелопер, грейд міддл.

Шалений Заяць, Божевільний Капелюшник, Ховрах Сонько – девелопери, грейд невідомий, оскільки ніхто не наважується провести їм оцінку. Посварилися зі Скрамом, тому в них кожен день реліз. Вони називають це контінюус деплоймент.

Чеширський Кіт – ніхто не знає точно, чим він займається на фірмі.

Дія Перша. Короткий зміст.

У пошуках консультації по задачі Аліса потрапляє в кімнату Шаленого Зайця. Там її відмовляються слухати, бо вона надто твереза. Після обіцянки випити все, що їй наллють, якщо тільки це буде вино, хлопці погоджуються вислухати її.

Дія Друга.

Колишній мітінг рум з великим столом по центру. Стіл заставлений макбуками, смартфонами, горнятками з/з-під чаю і кави, бокалами різного розміру і призначення, пляшками з розмаїтим алкоголем, в основному віскі та кон’яком, піцою, ковбасою, чіпсами, нарізками сиру по 500грн та іншим. Кімната захаращена купами коробок з-під піци і гірками порожніх плящок. Між ними розсипано комп’ютери, які загадково мигають світодіодами. Вся підлога оплетена місивом подовжувачів і мережевих кабелів. За столом сидять Капелюшник, Заяць і Ховрах. Останній спить положивши голову на макбук. Аліса стоїть поруч зі столом і явно боїться за нього сісти.

Аліса: Задача – реалізувати рестфул-ендпоінт, що видає юзеру аутх-токен. Перед видачею кукі є кілька перевірок на едж-кейси, частина з яких може бути розцінена як валідація, частина – як авторизація, а частина – як щось середнє…

Капелюшник: Валоризація!

Шалений: Авторідація!

Капелюшник і Шалений Заєць чокаються і випивають.

Аліса: Що краще – розкидати цю логіку по хукам фреймворка, чи винести в окремий метод-клас “інтерактор”?

Звідкись з’являється Чешир, витягує з купи порожніх пляшок напівповну пляшку Гленгойна, наливає в бокал і випиває.

Чешир: Гірше!

Аліса: Що гірше?

Чешир: Правильно питати не що краще, а що гірше. В ІТ немає хороших і кращих рішень – тільки погані і ще гірші…

Чешир виливає залишки віскаря собі в бокал і зникає.

Шалений: Сама що думаєш?

Аліса: Ну, плюс максимального юзання фреймворку – ми користуємось його фічами…

Капелюшник: Питання – чого ти хочеш більше: стікнутися намертво в фреймворк чи написати багато свого коду?

Капелюшник наливає в один бокал Хенессі, в другий Талібардін і пропонує Алісі вибрати.

Шалений: Свого коду більше не буде, буде менше, і він буде в одній купі.

Заяць забирає в Капелюшника бокал з віскі і залпом випиває. Потім забирає і бокал з кон’яком і починає розглядати як той грає в світлі лампи.

Шалений: А зі стіканням була абсолютно жутка історія, яка вилилась в 3-тижневий апгрейд фреймворка…

Капелюшник: Я взагалі проти залипання на фреймворки.

Капелюшник таки знаходить пляшку Мукузані, вручає Алісі практично чистий літровий бокал і виливає в нього всю пляшку.

Шалений: Ми юзаємо ДНР, і все було зав’язано на його фічі. Так от. З апгрейдом ДНР 2.0 на ДНР 3.0 вони поміняли все. Це було пекло.

Капелюшник: Ми тут другий рік не можемо випиляти бібліотеку, бо нею просто насрано по всьому коду.

Прокидається Сонько, наливає собі Мартель, випиває і закусує квашеним огірком.

Сонько: Це що за ліба?

Капелюшник: Сіквел.

Сонько: Проста ліба…

Сонько знову засинає, зручно вмостившись на клавіатурі.

Капелюшник: А якби воно було акуратно загорнуто у фольгу, то вже би давно випиляли ту просту лібу. Всього навсього оеремка.

Аліса: Але то породило б ще додатковий леєр.

Капелюшник: І? Ти віртуальна машина? Все програмування прагне до абстракцій все вищих і вищих порядків. До більшої кількості леєрів!

Капелюшник бере шматок холодної піци, мастить її маслом, зверху кладе сир, ковбасу, помідор і шматок оселедця. Потім показує всім свою канапку і з насолодую відкушує шматок.

Шалений: Погоджуюсь. Левел логіки має бути окремим, інакше розробка стане пеклом. Раніше думав – о, тут же можна заюзати такий прекрасний хук серіалайзера чи сигнал джанги… ага, щас! В наступній версії Джанго скаже що сигналів більше нема.

Аліса: Ну вони так не роблять – там декілька версій буде депрікейтед.

Шалений: Да-да , я теж так думав… до апгрейду днр.

Сонько знову прокидається і здивовано розглядає порожню пляшку з під Мартеля.

Сонько: Яка там джанга зараз?

Шалений: В нас джанга 1.9.

Сонько: Все – закреслюю свій досвід з жангою в резюме.

Шалений: Це неважливо. Важливо, що коли ти дивишся в код розпиханий по фічам фреймворка – ти не бачиш логіки коду.

Сонько: Код має бути тупим, а рішення геніальним. Так казала колись Герцогиня.

Капелюшник: Що означає, що код має бути читабельним, а рішення передбачуваним.

Шалений: Згоден! Юзання фіч фреймворка додає неявності і нелінійності. Без необхідності я б це для бізнес-логіки не юзав взагалі.

Сонько: Але це як наркотик – один раз спробував…

Шалений: Наркотик з жорстким бодуном!

Всі випивають.

Кінець.

Про юніттести і функціональні тести

Цей пост – вирізаний фрагмент дискусії зі Скайп-чату, що стосується дизайну софта. Всі учасники – люди з чималим досвідом.

Є таке питання. Є джанго-аплікейшн, який являє собою… РЕСТ-апішку.  Для чого потрібні юніттести на моделі, якщо можна зробити наскрізний функціональний тест?

Аргументація:
1. Для нас важливо чи працює АРІ, а не якісь внутрішні речі типу моделей.
2. Моделі в нас не реюзаються (поки).

Хочеться вашої думки за якийсь (або свій) варіант з аргументацією.
[11:20:38] Kostya: в моделях зовсім нема логіки?
[11:21:17] Wanderer: є повно логіки. Вся логіка там.

приклад:

є модель  User, в неї метод регістер, який “реєструє” юзера ще в 1 моделі (наприклад).  Можна тестувати це так: POST /users/, далі все перевірити.  Можна писати тести на модель і метод.
[11:22:42] Yuriy S.: окремі тести на апі і модель будуть менші, значить їх буде легше ментейнити. Також окремі тести на апішку будуть швидше працювати, бо все замокано. Для таких тестів не треба фікстур ніяких.
[11:23:25] Wanderer:
аргумент №1 – швидкість
аргумент №2 – розділення (інкапсуляція?)
?
[11:23:40] Yuriy S.: Десь так. Краще покриття тестами. Бо чи меньшу частину функціоналу охоплює тест, тим легше його написати правильно. Спробуй кількома великими тестами покрити апішку і модель зразу. Швидше за все ти протестуєш кілька основних випадків і все.
[11:25:23] Andrew Ko: http://henrikwarne.com/2014/09/04/a-response-to-why-most-unit-testing-is-waste/. Півроку тому був нехілий хайп в інтернеті про юзлесс юніт тестс.
[11:26:14] Wanderer: до чого прийшла організована програмістична спільнота?
[11:26:45] Andrew Ko:

почитай цю статтю: http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
А оце сама перша стаття (автор – автор рубі он рейлс)

[11:29:12] Wanderer: на перший погляд перша стаття згідна з Юрою (і мною):

Well-tested parts.
Decoupled design.
Rapid feedback.
[11:29:21] Wanderer: але не дочитав ще
[11:29:22] Andrew Ko: Вона більше про тдд але суть в тому шо юніттестити треба тілки дуже специфічні штуки. Алгоритми тощо. А все решта – функціональними.
[11:29:49] Wanderer: Ти згідний?
[11:30:08] Andrew Ko: По великому рахунку – так. Але це відправляє в смітник тдд. І хрєн з ним
[11:32:10] Wanderer: тдд і тести різні речі.
І це лише думка 🙂 Як на мене то тдд це цікава практика
[11:33:33] Andrew Ko: А я хіба сказав шо це одні і ті самі речі? я сказав шо тдд це написання юніттестів в першу чергу. якшо не писати юніттести на все то тдд іде лєсом.
[11:33:34] Wanderer: Слухайте, може це кьюа-лоббі? 🙂
[11:33:47] Wanderer: їх почали заміняти тестами і тут змова 🙂
[11:34:32] Andrew Ko: > І це лише думка

це думка мартіна фавлєра, дхх. Тобто крутих пєрців проти думки яких моя думка ніщо.
[11:34:59] Wanderer: Я розумію, але 10 років тому його думка була та ж сама? А якою вона буде завтра? Доктрина міняється з часом.
[11:35:28] Andrew Ko: якби думка не мінялася ми б досі писали на асемблері або в машинних кодах.
[11:35:47] Wanderer: думка щодо корисності оод не міняється вже давно. Кити стоять принаймні. Тести прийшли досить недавно, і цю галузь поки штормить.
[11:36:47] Andrew Ko: what?
[11:37:38] Wanderer: В мене є думка, якої я не можу позбутись. Чи не порушує такий принцип стандартний підхід знизу вгору – побудови з блоків? Користуючись цією логікою можна не писати ніяких класів (не в тестах) і забити на всі принципи типу солід. На бізнес вимоги це не впливає жодним чином. Ні? Тобто заперечення юніттестів легко апроксимувати на сам код.
[11:39:11] Andrew Ko: Бізнесу пофіг шо там в тебе в середині солід не солід.
[11:39:26] Wanderer: Ось. тоді не лише юніттести, але і все ООП в топку. З усіма дизайнами.
[11:40:06] Andrew Ko: Бізнесу по барабану ооп в тебе там всередині чи код на бейсіку. Головне щоб система хавала інпут і випльовувала аутпут весь час коректно.
[11:40:23] Wanderer: і люди придумали оод щоб…
[11:41:06] Andrew Ko: ООП придумали не люди, а програмісти. Щоб їм легше було програмити. Щоб не путатися в пейдждаунах іф елсе.
[11:41:42] Wanderer: А юніттести вони придумали щоб… знати чи правильно попрограмили? Принцип інкапсуляції. Я пишу клас за спекою, віддаю. Хто і як його юзає – побоку. Хіба відсутність юніттестів це не порушує?
[11:42:21] Andrew Ko: а юніт тести вони придумали шоб було потім легше шашкою в коді махати
[11:42:40] Wanderer: Вот. І тоді Мартін Фаулер що… неправий? Або його не так зрозуміли?
[11:42:55] Andrew Ko: А, ну якшо зводити софтвер девелопмент до написання класів… Тобто якшо вважати класи кінцевим продуктом, а не побічним артефактом.
[11:45:24] Wanderer: Класи – засіб боротьби зі складністю. Суть їх – розбиття на незалежні блоки і побудова продукту знизу вгору (канон програмування).
Тести на класи – боротьба за помилками в цих незалежних блоках і інструмент для підтримки їх правильності і відповідності вимогам.
Мені здається, що вищезгадана анафема юніттестам це порушує.
[11:46:09] Andrew Ko: Бізнес не цікавить складні в тебе класи, солід вони чи ні. Якшо в тебе є анлім грошей і часу – пиши юніт тести на кожен чіх.
[11:46:48] Wanderer: мене як користувача не цікавить що в тв є транзистори. І що це означає, що їх не має там бути?
[11:47:00] Andrew Ko: Якшо в тебе бюджет розумний то (бізнесу) достатньо коли затестована зовнішня поведінка. Компроміс.
[11:47:09] Wanderer: Це означає що будувати ТВ інженери можуть як їм краще. Цікаво щоб високошановні присутні висловились, дві точки зору ніби є. Навіть три. А з Мартином – чотири.
[11:48:52] Andrew Ko: On 1/14/15, at 11:46 AM, Wanderer wrote:

мене як користувача не цікавить що в тв є транзистори. І що це означає, що їх не має там бути?

так. там можуть бути лампи, транзистори, мікросхеми, волшебний мох. Споживачам телевізорів байдуже.
[11:49:07] Wanderer: ключове слово можуть
[11:49:26] Andrew Ko: Ні, ключове – споживачам байдуже.
[11:49:38] Wanderer: Я не заперечую транзистори. Мені просто байдуже. Ми говоримо про те як інженерам краще робити ТВ (писати код рест арі). Ми ж інженери, тобто думаємо про побудову.
[11:55:19] Andrew Ko: http://blog.stevensanderson.com/wp-content/uploads/2009/11/image.png
[11:57:22] Wanderer: чому  люди так люблять чорно-білу палітру і едж-кейси 🙂 Певно тяжіють до бінарного мислення.
Я не маю на увазі тебе, Ендрю, це загальне спостереження.
Мене не цікавить ні low ні high. Більшість коду всередині. Все як в житті.
[11:59:35] Andrew Ko: а ти там бачиш тільки чотири квадратика і всьо? 🙂
[12:00:03] Wanderer: low && high. Де average? Півтонів нема.
[12:02:52] Kostya: по-моєму з юніт-тестами головне не страждати фанатизмом, створюючи їх на кожну дрібницю. Покриваючи ними основний функціонал і edge case-и можна витрачати не так багато часу, і при розробці зручно. Бо часто їх пишуть більше, ніж треба, і потім виявляється що велика частина із них нічого не тестує, або тестує одне й те саме по багато разів.
[12:04:27] Yuriy S.: спостереження з власного досвіду – нормально покрити апішку функціональними тестами дуже тяжко – постійно вилазять якісь проблеми, але це легко вирішується написанням юніттестів, які, як не дивно, пишуться трохи легше за функціональні, а працюють набагато швидше.
[12:06:08] Wanderer: мейкс сенс ту мі. Типу якщо тестів на колеса нема, а на машину є. Видає: помилка – не їде (колесо не крутиться). Розібрали – колесо квадратне.
Юніттест зловив би це швидше…
[12:14:04] Andrew Ko: Питання тільки в тому, що написати юніттест на машину швидше/дешевше чим на кожну гаєчку в автомобілі. Відповідно юніттести мали би бути на важливі агрегати (якшо ти будуєш не космічний корабель вартістю мільяри доларів). Що власне та діаграма (на мою думку) і відображає.
[12:17:14] Wanderer: Діаграма каже: тести на гайки не дуже потрібні, тести на колеса – в 100 разів потрібніші, тести на двигуни – в 1.000.000 разів потрібніші. Тому і претензія до бінарності картинки.
[12:18:28] Andrew Ko: Ти сам собі суперечиш. Ти назвав 3 значення потрібності. Хоча там не про потрібність, а про дві величини – корисність і вартість. Тобто на двовимірній площині кожен знаходить точку з потрібними координатами.
[12:19:48] Wanderer: Це гра в слова? Потрібність не корелює з вартістю і корисністю?
[12:20:18] Andrew Ko: для мене ні
[12:20:40] Wanderer: Біда картинки в тім, що код – штука небінарна. Він в більшості випадків лежить всередині картинки. Виходить що твердження “юніттести не потрібні” – невірне. Більшість коду і не так щоб тривіальне і не так щоб багато алгоритмів, і не так щоб багато інтеракцій. Середній код.
[12:22:24] Andrew Ko: Більшість коду навпаки тривіальна. Всі всьюшки тривіальні. Нетривіального коду мало.
[12:23:03] Wanderer: В джанго логіка не у вьюшках, звісно що вони тривіальні.
[13:39:58] Ендрю С.: IMNSHO

1) Корисність юніт теста обернено пропорційна кількості замоканих об’єктів при його написанні.
2) Ідеальний об’ект для юніт тестів – чиста фунція.
3) Бізнесу не пофіг на вартість додавання нових фіч – економічно це чиста задача на ROI — автотести це інвестиція.
4) юніт тести покривають сабсет того що можуть знайти інтеграційні/функціональні тести, тому коли доходить до вибору між ними то функціональні рулять
5) питання того що функційні тести довші вирішуються більшою к-стю заліза, шо є дешево
[13:42:24] Wanderer: Ага, і ще можливий випадок, коли ми перемкнемось на інший фреймворк, і отримаємо абсолютно нетестовані моделі (у випадку мвс) з невідомою поведінкою. А от якби були юніттести на моделі…
[13:44:41] Ендрю С.: А якщо є функ. тести на АПІ то взазалі пофіг якого рівня зміни коду ми робим всередині 😉
[13:45:00] Taras: скільки функціоналу покривають функ тести?? Хтось бачив покриття таких тестів ну хоча б 20%?
[13:46:31] Wanderer: Ну і да, тести таки круто показують коли код штиняє. Юніттести.
[13:46:44] Ендрю С.: Я і юніт тестів більше 50% не зустрічав в природі, но слухи ходять шо і ті і інші бувають суттєво вищі 🙂
[13:46:57] Andrew Ko: Тарас, всі критичні флови як правило покриті. Все, що не покрито тестами, покрито qa. 100% покриття коду тестами це взагалі єрєсь для багатих буратін.
“а якщо є функ. тести на АПІ то взазалі пофіг якого рівня зміни коду ми робим всередині ;)”

(y)  хоч на бейсіку все перепиши
[13:50:22] Yuriy S.: Я б так не сказав. На практиці це не підтверджується.
[13:50:22] Wanderer: Тести на АРІ банально складніші, а все програмування – це прагнення розбити код на прості чанки.
[13:53:06] Ендрю С.: Мікро-сервіси на допомогу, власне в цьому контексті вони рулять.
[13:54:24] Wanderer: Якщо сервіс настільки мікро, що те що під ним не вимагає юніттестів – питань нема. Але я такого не зустрічав.

Using Recast to Automate Analysis and Maintenance of JS Code

Originally published at: 60devs.com

Recast is a great library for parsing and modifying JavaScript code. The result of the parsing is an Abstract Syntax Tree (AST) which is easy to traverse and analyze. Once a tree is built, it’s easy to modify it and convert back to the source code. Additionally, Recast provides methods for pretty printing ASTs as well as API to construct new ASTs without parsing any source code.

Here are some use cases for Recast:

  1. Static code analysis
  2. Generation of documentation based on source code comments
  3. Code formatting
  4. Automatic code modification

The last one is actually the main use case and Recast was created for this:

The more code you have, the harder it becomes to make big, sweeping changes quickly and confidently. Even if you trust yourself not to make too many mistakes, and no matter how proficient you are with your text editor, changing tens of thousands of lines of code takes precious, non-refundable time.

Specifically, my goal is to make it possible for you to run your code through a parser, manipulate the abstract syntax tree directly, subject only to the constraints of your imagination, and then automatically translate those modifications back into source code, without upsetting the formatting of unmodified code.

says Ben Newman

It’s very easy to start with Recast.

Getting Started with Recast

The ultimate sources for learning Recast are these two repositories: https://github.com/benjamn/recast and https://github.com/benjamn/ast-types
The first one contains a lot of information about Recast’s own API and the second one explains how to work with ASTs.

Simple example demonstrates how to parse the source of a JS function (source: https://github.com/benjamn/recast):

var recast = require("recast");
var code = [
    "function add(a, b) {",
    "  return a +",
    "    // Weird formatting, huh?",
    "    b;",
    "}"
].join("\n");
var ast = recast.parse(code);

As you see, the source code was not formatting properly. Recast can fix this easily:

var output = recast.prettyPrint(ast, { tabWidth: 2 }).code;
console.log(output);

The output is:

function add(a, b) {
  return a + b;
}

That’s it – source code formatting is covered. Now let’s rewrite the source function using AST builder API:

// Grab a reference to the function declaration we just parsed.
var add = ast.program.body[0];
var b = recast.types.builders; // builders help build AST nodes

// declare a var with the same name as the function
ast.program.body[0] = b.variableDeclaration("var", [
    b.variableDeclarator(add.id, b.functionExpression(
        null, // Anonymize the function expression.
        add.params, // params
        add.body // and body are left unchanged
    ))
]);

// Just for fun, because addition is commutative:
add.params.push(add.params.shift());

The output of this transformation is:

var add = function(b, a) {
  return a + b;
}

Extracting Source Comments Using Recast

First, we need to parse the source code like in the previous example. Next, we have to find all comments. This can be done using visit API:

recast.visit(ast, {
  visitComment: function(path) {
    console.log(path.value);
    this.traverse(path); // continue visiting
  }
});

This prints out all comments in the tree. When using visit API, you need to call either this.traverse to continue traversing or this.abort() to cancel traversing.

All functions of the visit API follow the same convention: visit. For example, visitCallExpression, visitFunction are valid callbacks.

Generating Documentation Based on Comments

The previous method of getting comments has one drawback: you get comments for all parts of the code and you need to figure out what part comments relate to. There is another possibility: first, find nodes you are interested in and, second, get comments related to these nodes.

For example, let’s get comments for all object properties:

var recast = require("recast");

// our object with commented properties
var code = [
    "var obj = {",
    "/** this is my property */",
    "myProperty: 'string'",
    "}"
].join("\n");

var objAst = recast.parse(code);
attributeComments = [];

recast.visit(objAst, {
  visitProperty: function(path) {
    if (path.value.comments) {
      attributeComments.push({
        name: path.value.key.name,
        comments: path.value.comments.map(function(c) {
          return c.value;
        }).join('\n')
      });
    }
    this.traverse(path);
  }
});

console.log(attributeComments);

The attributeComments array will contain all properties of the object and related comments:

[ { name: 'myProperty', comments: '* this is my property ' } ]

Printing Out Entire AST

For learning it may be useful to inspect the entire tree, when you are not familiar with AST types:

var util = require('util');
console.log(util.inspect(objAst, { showHidden: false, depth: null }));

Switching Default Esprima

Sometimes the default Esprima used for parsing(esprima-fb) may fail. For example, esprima-fb failed to parse ES6 statements like const { obj } = otherObj;. Recast allows you to change the default parser:

npm install babel-core
var babelCore = require('babel-core');
var ast = recast.parse(data, {esprima: babelCore});

Finally, here are some projects that show how to use Recast: Ember Watson, Commoner and others. Ember Watson was especially helpful/inspiring for me.

Thanks for reading and I hope you will make use of such a great tool as Recast.

Executing JavaScript Code in a Sandbox Using Node’s VM Module

The API of Node exposes a module called VM that allows for a more safe execution of arbitrary JS code. The module operates in terms of scripts, sandboxes and contexts.

  • Scripts are objects that represent compiled versions of JS code.
  • Sandboxes are plain JS objects that will be bound to a script before the script is executed. At runtime, the scripts will have access to the sandbox object via the global object.
  • Contexts are sandboxes prepared for usage by the VM module. It’s not possible to pass a sandbox directly to VM. First, it needs to be contextified. The resulting object connects the sandbox and the script’s runtime environment.

How to create a script?

const vm = require('vm');
const script = new vm.Script('throw new Error("Problem");', {
filename: 'my-index.js', // filename for stack traces
lineOffset: 1, // line number offset to be used for stack traces
columnOffset: 1, // column number offset to be used for stack traces
displayErrors: true,
timeout: 1000 // ms
});

How to run a script?

There are severalImmediately-Invoked Function Expression options how to run a script and they depend on which context the running script should have:

  • script.runInThisContext(opts) – runs the script in the current scope, i.e. the script will have access to the global variable of the current script but not to the local scope.

  • script.runInNewContext(sandbox, opts) – runs the script in the scope of sandbox, i.e. sandbox.a will be exposed as a inside the script.

  • script.runInContext(context, opts) – runs the script in the context context where the context is the result of vm.createContext on some sandbox object. I.e. runInNewContext calls vm.createContext for you whereas with script.runInContext you can provide a sandbox that was previously contextified by you.

Also it’s possible to omit creation of the script object and use the same methods but on the vm module to create and run scripts in a single step:

const vm = require('vm');
vm.runInThisContext(code, opts);
vm.runInNewContext(code, sandbox, opts);
vm.runInContext(code, context, opts);

Performance

The code in the sandbox can run slower than normally if you create a new context and use global lookups on that context. For example, the following test code will produce the following results (node v5.4.0, default params):

'use strict';
var code = `
// global script scope
function nop() {
};
var i = 1000000; // global script scope
while (i--) {
nop(); // access global scope
}
`;

console.time('eval');
eval(code);
console.timeEnd('eval');

var vm = require('vm');
var context = vm.createContext();
var script = new vm.Script(code);

console.time('vm');
script.runInContext(context);
console.timeEnd('vm');

Results:

eval: 11.191ms
vm: 648.014ms

To avoid this, wrap scripts that you run using runInContext or runInNewContext in a immediately-invoked function expression (IIFE).

Safety

Using vm’s module is more safe than relying on eval because scripts run by the vm module do not have access to the outer scope (or do have access only to the global scope as with runInThisContext). Still the scripts run in the same process so for the best security it’s advised to run unsafe scripts in a separate process.

Also by default the scripts do not have access to things like require and console. If you want to allow them, you have to provide it explicitly:

const vm = require('vm');
vm.runInNewContext(`
var util = require('util');
console.log(util.isBoolean(true));
`, {
require: require,
console: console
});

Thanks for reading.

Spring DATA + Mongo. Testing using in-memory db

Embeddable

When I was a bit younger, I couldn’t understand why more senior engineers stood for embedding builds as much as it’s possible. For instance, you have a database, and you’re running your integration tests only with in memory databases. It was very unusual for me, why having a working database on your local machine, you’re using some weird in memory things ? The time have passed, and I understand now, that embedding your builds is a good practice, because:

  • It reduce your build time
  • It decouples your build phase from any environments
  • Even if you don’t have any database/thirdparty tool installed on your local machine, your build will finish successfully, and after then you can start installing all required third party instruments.

Fongo + NoSQl-Unit

By this article I’d like to show how to effectively test your Spring DATA repositories using Fongo – an in-memory implementation.

I’m not going to explain how Spring Data works, you can read their documentation here

Say, you have following repository

package org.example.repository;  
import org.springframework.data.mongodb.repository.MongoRepository;  
import org.springframework.data.mongodb.repository.Query;  
import org.startup.queue.domain.Establishment;

import java.util.Optional;

public interface SomethingRepository extends MongoRepository<Establishment, String> {

    Optional<Something> findByCol1(String col1);

}

Next, test it:

package org.example.repository;

import com.github.fakemongo.Fongo;  
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;  
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;  
import com.lordofthejars.nosqlunit.mongodb.MongoDbRule;  
import com.mongodb.Mongo;  
import org.junit.Rule;  
import org.junit.Test;  
import org.junit.runner.RunWith;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.ComponentScan;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.PropertySource;  
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;  
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;  
import org.springframework.test.context.ContextConfiguration;  
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;  
import org.startup.queue.domain.Establishment;  
import org.startup.queue.domain.Table;

import java.util.Collections;  
import java.util.List;  
import java.util.Optional;

import static com.lordofthejars.nosqlunit.mongodb.MongoDbRule.MongoDbRuleBuilder.newMongoDbRule;  
import static org.junit.Assert.assertEquals;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SomethingRepositoryTest {

    // Don't forget to add this field
    @Autowired
    private ApplicationContext applicationContext;

    @Rule
    public MongoDbRule mongoDbRule = newMongoDbRule().defaultSpringMongoDb("demo-test");

    @Autowired
    private SomethingRepository unit;

    @Test
    @UsingDataSet(locations = "somethings.json", loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
    public void testFindByTablesQr() throws Exception {
        // Given
        Something expected = new Something();
        ... data from somethings.json

        // When
        Optional<Something> actual = unit.findByCol1(col1);

        // Then
        assertEquals(expected, actual.get());
    }


    @Configuration
    @EnableMongoRepositories
    @ComponentScan(basePackageClasses = {SomethingRepository.class})
    static class SomethingRepositoryConfiguration extends AbstractMongoConfiguration {


        @Override
        protected String getDatabaseName() {
            return "demo-test";
        }

        @Bean
        public Mongo mongo() {
            Fongo queued = new Fongo("something");
            return queued.getMongo();
        }

        @Override
        protected String getMappingBasePackage() {
            return "org.startup.queue.repository";
        }

    }
}

Let’s explain:

    @Autowired
    private ApplicationContext applicationContext;

By this line you are forcing Spring Context to fully load under this class. If you’ll skip this line – mongoDbRule won’t work.

    @Rule
    public MongoDbRule mongoDbRule = newMongoDbRule().defaultSpringMongoDb("demo-test");

By adding this line you’re making sure, that you can use NoSQL-Unit. I mean, adding this line will allow you to use
@UsingDataSet annotation. Of course, you can use Fongo without NoSQL-Unit. In this case you will need to manually add records into your nosql storage. Refer to github of following project to understand all possibilities of this project.

    @UsingDataSet(locations = "somethings.json", loadStrategy = LoadStrategyEnum.CLEAN_INSERT)

By line above you are loading your collection data into your in memory database.
PS – your something.json should be under src/test/resources/org/example/repository/something.json folder.

        @Bean
        public Mongo mongo() {
            Fongo queued = new Fongo("something");
            return queued.getMongo();
        }

How your in memory database is being created ? You need to override spring bean, which stands for creating Mongo object.

That’s all what you need to do to test your mongo repository. I’ve noticed, that testing Spring Data using in-container mode is the only reasonable way to test them.

Original post

Мої доповіді, презентації, пакети та ідеї щодо платформи Сейлзфорс

 

Нещодавно я підготував кілька презентацій.
У першій презентації йшлося про Salesforce REST API.

Пізніше я переробив її і знову завантажив у новій версії

і навіть зняв коротке відео, де коментував цю версію:

Крім того, я підготував ще одну презентацію про те, як бути Сейлзфорс джедаєм про деякі заборонені і передові технології

, а також зняв відео з коментарями до презентації:

У цих двох презентаціях я згадав деякі пакети, які я вже створив або ідеї пакетів, які я можу створити в майбутньому, якщо у мене буде достатньо натхнення.

Отож пізніше я зняв кілька відео, які демонструють мої пакети в дії.
REST API Demo (ідея пакету)

ULETAS Гамма (випущений пакет)

Job Manager (випущений пакет)

Смарт S2S Шаблон (ідея пакету)

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

 

Оригінальний пост.