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


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 
Показаны сообщения с ярлыком tdd. Показать все сообщения
Показаны сообщения с ярлыком tdd. Показать все сообщения

пятница, 7 июня 2024 г.

Научный подход: Что лучше подтверждать многократно или опровергнуть 1 раз?

Такой вот диалог состоялся сегодня. С позволения коллеги опубликую его часть. Спасибо собеседнику за чудный диалог!

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

Q: ... это гипотеза, и она может стать самоисполняющимся пророчеством, если ее попробовать подтверждать, а не не опровергнуть. Знаешь этот баяс?

A: давай)

Q: Есть загадка. Есть некоторая закономерность. Вот ее три числа из нее: 2 4 8.
Твоя задача:
1) или сказать мне какая закономерность мной задумана
2) или предложить 3 другие цифры ряда чтобы проверить подходит или нет
Я могу только так делиться с тобой инфой
на 1) вопрос "да, это загаданная мной закономерность" или "нет, это не та закономерность что я загадал"
и на второй вопрос "да, эти числа подходят под загаданную закономмерность" или "нет, не подходят"

A: арифметичская прогрессия ?)

Q: нет

A: цифры на numpad в виде ромбика?

Q: нет

A: 16 32 64

Q: подходит под мою закономерность что я загадал

A: чет я изначально подумал там 2 4 6 8, а оказся просто степени двойки)

Q: нет не степень двойки

A: ну я угадал 2 часть? нужно теперь закономерность? просто каждое число это предыдущие 2 помноженные, забыл как называется)

Q: нет это не то что загадано

A: типа фибоначчи только перемноженные. а нет, не подходит

Q: нет не чиса фибоначчи загаданы

A: 32 256 8192

Q: 32 256 8192 - подходит

A: может это просто любые четные числа?

Q: нет, не эту закономерность я загадал

A: я сдаюсь)

Q: я сейчас умничаю чуть, но так же было не просто. потом ты сможешь сделать то же с другими ребятами

A: алгоритмы не мой конёк)

Q: смотри один момент.
можно докзывать гипотезу, пытаться подтвердить ее тестами, которые сразу зеленые.
а можно попробовать опровергнуть гипотезу, и попытаться найти тест, который будет красный

A: то есть мне следовало сначала попробовать неверную комбинацию?

Q: у тебя есть идея, например степень двойки
и вместо того, чтобы ее подтвержать
2 4 8
16 32 64
128 256 512
что все будет да да да, напиши один тест, который опровергнет ее и посмотри что будет

A: а ты как среагируешь - скажешь что подходит?

Q: давай проверим - зависсит от того что я загадал

A: 9 6 3

Q: это не подходит
идея в том, что 1 фейл тест сразу отсекает гипотезу, но 10000 саксес тестов не делают того же

A: ага

Q: а баяс в том, что люди и их метод познания мира заточен by default под подтверждение - сразу выдвинуть гипотезу, посмотреть что она работает и погнали, а она объясняет только часть модели мира, и то очень поверхностно

A: есть вроде какой-то вид тестирования, в основе которого такой принцип

Q: Test Driven Development с его fail first

A: нет, я про сначала все опровержения, т.е. негативные кейсы)
то есть у тебя реальная закономерность

Q: да

A: ну мне только брутфорсом остаётся)
0 2 4

Q: подходит

A: 4 8 3

Q: 16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false

A: нечётные - падают

Q: а попробуй опровергни эту гипотезу?
она рабочая у тебя - и сразу могу сказать что это не то что я загадал, но попробуй это тестом покажи

A: опровергнуть гипотезу что все четные - true?)

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

A: можно ограничиться 2 тестами
1 1 1
2 2 2
первое упадет второе пройдет

Q: 16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false
1 1 1 - false
2 2 2 - false

A: хм
значит и порядок важен?
пробуем 2 3 4

Q: и это тоже гипотеза, опровергни ее
16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false
1 1 1 - false
2 2 2 - false
2 3 4 - true

A: 2 4 3

Q: 16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false
1 1 1 - false
2 2 2 - false
2 3 4 - true
2 4 3 - false

A: значит не связано с чётностью, а только порядок важен

Q: это гипотеза
или тест, или закономерность
я смогу провалидировать
даешь больше fail тестов!

A: 0 0 1
будет много тестов)

Q: 16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false
1 1 1 - false
2 2 2 - false
2 3 4 - true
2 4 3 - false
0 0 1 - false

A: 0 1 0

Q: отлично
16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false
1 1 1 - false
2 2 2 - false
2 3 4 - true
2 4 3 - false
0 0 1 - false
0 1 0 - false

A: надо проверять каждую позицию
1 0 0

Q: 16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false
1 1 1 - false
2 2 2 - false
2 3 4 - true
2 4 3 - false
0 0 1 - false
0 1 0 - false
1 0 0 - false

A: 1 2 0

Q: 16 32 64 - true
32 256 8192 - true
9 6 3 - false
0 2 4 - true
4 8 3 - false
1 1 1 - false
2 2 2 - false
2 3 4 - true
2 4 3 - false
0 0 1 - false
0 1 0 - false
1 0 0 - false
1 2 0 - false

A: 9 2 3

Q: ...
9 2 3 - false

A: 1 2 3

Q: 1 2 3 - true

A: ну всё я уверен
есть закономерность
каждое число больше предыдущего
я проверил все 3 позиции

Q: верно!
смотри что интересно, когда ты начинаешь генерить тесты которые будут false по твоему ты можешь получить diff между я думал А (false), а на самом деле Б (true)
вот так отсеиваются гипотезы
в этом суть научного подхода
если ты генеришь тесты которые true - то скорее всего ты будешь получать подтверждения гипотезы, что не доказывает ее правдивость
хоть 10000000 раз подтверди

A: ну это примерно как проверяют вакцины
согласен, проще сначала опровергнуть

Q: все потому что одно может включать другое просто

Q: и твоя гипотеза ок, как частный случай
а есть другие люди
и тут двоим ребятам может показаться, что не прав тот кто точно ничего общего не имеет с вашим пересечением

A: и вроде у всех всё сходится, и у каждого своя правда

Q: так и живем
одними и теми же словами описывая разный опыт
потому что у Homo Sapiens есть баясы
их много вообще


Q: А про научный подход есть видео хорошее, очень рекомендую

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

понедельник, 17 января 2022 г.

Инженерные практики могут ускорить тебя в 100х раз

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

Почему я коллекционирую различные ускорялки? Математика проста. Пусть это конкретное решение мне даст +15% в производительности. Какое-то другое решение даст +15%. Еще где-то что-то прочитаю и попробую, снова +15%. Складываем вместе 10 подобных решений и получаем 1.15 ^ 10 = 4.04. То есть 10 подходов ускоряющих тебя всего лишь на 15% в сумме дают + 404%, а это 4х в скорости! Подходов за 10 летний опыт может собраться на порядок больше. И для 50 штук прирост будет уже 1083%, 10x. А и это 15% роста продуктивности это так, самый минимум из того, что новая практика тебе может дать. Часто введение нового подхода сама по себе в разы тебя ускоряет. 

Внимательний читатель заметит, что любой новый подход в разработке облагает автора налогом. Естественно надо закладывать время на суппорт. Часто разработчик принимает решение сам, буду ли заморачиваться с новым кодом и его суппортом или по старинке сделаю, и делает это на основе критерия. Если я скажу, что новый подход даст тебе прирост в производительности в 10 раз, а при этом потребует от тебя всего лишь по 5 минут в день больше времени - дурак не согласится. Но обычно все не так. Времени на реализацию решения надо потратить сегодня 2-3 часа, а потом еще на суппорт решения раз в неделю по 0.5 часа. А прирост будет 10-20%, что уже не так леко прочувствовать, как 2,5 часа. На одной чаше весов - вклад в часах единоразовый с небольшим вниманием, а на другой % прироста производительности. % складываются иначе, чем часы. Копить % выгоднее, чем экономить время. Не всегда, но часто.

Правда, далеко не все дивиденды от инженерных полдходов можно "складывать" как описано выше. Есть независящие инструменты. Ну например я кодирую в Idea (она удобнее), а на сервере у меня все в docker (легче чем инсталить все на host машину). Там на X% ускорился, тут на Y%. Но так как работаю я ИЛИ в идее ИЛИ с докером на сервере, то % берется не от 168 рабочих часах в месяце, а от всего времени в IDE и отдельно всего времени во время обслуживания сервера. Результат складываем и "X% + Y%" дает ((1+X/100)*develop_time + (1+Y/100)*deploy_time). И в этом случае время проинвестированное в разработку и поддержание инструмента стоит оценивать критичнее. Но есть и фундаментальные подходы, скажем как следование принципам BabyStepsRefactoring, CleanCode, UnitTesting, TestFirst. Выгода от использования этих принципов ощущается одновременно и влияет друг на друга, потому формула суммирования будет более вкусной. Возможно не (1+X/100)*(1+Y/100) - это крайность. Но чем более влияния подходов друг на друга, чем чаще они используются, тем ближе мы к пермножению процентов, а не суммированию.

Надеюсь никтон не будет спорить в наше время с тем фактом, что рефакторить код необходимо, если ты хочешь хоть как-то влиять на энтропию системы. Держать код в чистоте - значит помогать себе и другим читателям понимать, что тут происходит. Делать рефакторинг можно грубо зарывшись по уши в код и потом тратя время на исправление всех ошибок компиляции, отловку багов, а можно элегантно - вооружившись компилятором ide и юнит тестами быстро привести код в рабочий вид. Но параллельно с этим всем можно производить рефакторинг маленькими шажками, что позволит исключить дебаг из процесса. А если нужна новая функциональность, то использования подхода TestFirst и сключит дебаг так же из процесса разработки. Привет TDD! Каждая из практик ценна сама по себе, но вмсте они дают возможность избавиться от Debug вообще. 

Debug - самая сложная и неуправляемая, а потому и дорогостоящая часть в разработке. Во время Debug ты мало что понимаешь - только тыцаешь 2-3 клавиши и смотришь в стек в ожидании инсайта. А без юнит тестов рефакторинг опасен, но если ты и осмелишься - много багов уйдет на прод. А без рефакторинга сложно и за CleanCode следить. Так вскоре техдолг не даст менять сисему как того нужно бизнесу. В какой-то момент исправление 1 баги будет порождать 2 новых. Тогда лучшее, что можно сделать - заморозить его. Ну или покрыть модуля автогенерируемыми юнит тестами, затем взяться за рефакторинг модулей с целью навести порядок в модели и сделать код чуть более clean. Затем выкинуть старые автогенерируемые тесты и написать нормальные, читабельные. Этот долг придется выплатить, если хочется продлить жизнь проекту. И лучше тут пользовать подходом ПрицнипСкаута. Сделать место стоянки после себя чище, чем оно было до тебя. Каждый день плати чуть-чуть времени уделяя этим инструментам: CleanCode + Refactoring + UnitTesting. И проект проживет дольше. 

Это один из примеров связки практик. Есть и другие практики. И тот из нас, кто научится использовать их по максимуму будет производительнее чем тот, кто не будет в 100 раз, в 1000 раз, а может и в 10000 раз. Возьмем студента новичка без опыта разработки - сложная задача может просто его застопорить на недели (если вообще задача будет решена), тогда как опытный миддл сделает ее за два дня. Вот и решай на сколько более производительный тот, кто сделал работу в сравнении с тем, кто не сделал ее. Я же верю в то, что хоть в среднем по индустрии Middle не сильно отличается от Senior (так просто устоялось), их производительность может отличаться в весятки, а то и сотню раз. Вопрос в том, остановился ли Senior в развитии на уровне инструментария Middle и дальше качает только SoftSkills или продолжает поиск инструментов его ускоряющих.

среда, 12 января 2022 г.

Нейросети уже кодят вместе с нами

Ждал эту фичу от StackOverflow но ее предложил Github.  Программирование с этой технологией становится еще более высокоуровневым.

Какие трудности. Ну кроме того, что еще больше информации будет собирать о нас большая компания добра (этим в наше время уже никого не напугать) больше волнует общественность нарушение авторских прав. Во-первых копайлот учится на открытом исходном коде, а некоторые лицензии могут запрещать использование этого кода в проприетарном приложении (привет GNU GPL). Во-вторых код из окружения разработчика утекает на сервера Microsoft и вероятно на них тоже обучается нейросеть, что может нарушать авторские права проекта. Но прогресс не остановить, я уверен что у ребят найдутся ресурсы, чтобы порешать все и технология увидит своего покупателя. Кстати да, я более чем уверен что подписка будет платной. 

Слышу ворнинги разработчиков порталов с коллекцией алгоритмов типа HackerRank, ведь теперь любому участнику в разы проще вырваться из низов используя только Copy-Past Driven Development. Но я уверен, что и они решат этот вопрос.

Что классно - что порог вхождения заметно уменьшился. Теперь не надо будет учиться гуглить на StackOverflow новичку в программировании (с этим замечены были трудности), но все так же надо будет учиться декомпозировать, отлаживать, рефакторить, ООП и т.д. 

Мне видится потенциал этой технологии в области TDD и Refactoring. Уже сейчас такие чудные инструменты как Intellij Idea очень классно подсказвают в рефакторинге что можно упростить. И те из нас, кто очень крут в рефакторинге просто потому что он его изучал глубоко постепенно сдает свои позиции тем, кто только вошел в индустрию. Два клика мышкой и смотришь - было стало. А теперь представь, что сможет Copilot глядя на все Diiff во всех репозиториях - там закодированы бесчисленные рефакторинги. 

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

Будущее уже наступило. Пристенитесь, мы продолжаем ускоряться.


четверг, 14 ноября 2019 г.

Почему TDD дается сложно инженерам?

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

Три недели назад я публиковал пост на FB о критерии фальсифицироемости с научно-популярным объяснением от Veritasium. Пару дней назад мы обсуждали с коллегой старый добрый TDD подход. А на утро следующего дня повторяя эту чудную загадку от Veritafium в разговоре с другим коллегой, бам - инсайт. 

Проведи эксперимент над друзьями. Предложи раскрыть задуманную тобой закономерность, сообщать о которой ты можешь только отвечая true / false на три числа, которые по мнению отгадывающего должны соответствовать этой закономерности. В качестве вводной ты называешь 2, 4, 8 и говоришь, что эти числа соответствуют задуманной тобой закономерности. Дальше отгадывающий должен подумать и если у него есть идея что это за закономерность - предложить три числа, которые соответствуют ей. С него три числа, а с тебя true или false. Дальше на оригинальном видео (можно включить русские субтитры)



Что получается, большинство людей придумав гипотезу, сразу же стараются ее доказать. Видимо в этом by default поведении была какая-то потребность в процессе эволюционного развития мозга (а может и сейчас есть). Но такой подход приводит людей часто ко всяким лженаукам. Так гипотеза очень быстро превращается в теорию, если доказывать только ее правдивость или вскоре принять на веру. 

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

"Принцип фальсифицируемости противоположен принципу верифицируемости. При верификации гипотезы исследователь ищет подтверждающие её примеры, при фальсификации — примеры, опровергающие её." (с) Википедия

Чтобы все было по-научному, надо как минимум чтобы то что тобой придумано могло быть опровержимым экспериментально (или как-нибудь еще). То что очень большое число фактов подтверждает твою идею о том, как устроен мир, делает ее весьма вероятной, но не на 100% достверной. Достаточно всего лишь 1 опровергаюего факта, чтобы идею можно было отклонить как ложную. А вот это уже неприятно. Ведь то, что придумалось мной, такое мое, такое "правильное", а все кто не согласен с этим просто глупые, чтобы понять это. Так и носимся с идеей, как курица с яйцом. А могли бы откинуть сразу и заняться поисками чего-то по-настоящему рабочего. 

TDD как раз заставляет относиться к своему коду (твоей гипотезе, которая описывает желаемый заказчиком результат) как к чему-то скорее всего неполному и требующему доработки (недоделанные кейзы, ошибки в понимании ТЗ, ошибки в реализации, ошибки интеграции и т.д.). Ты пишешь тест, чтобы найти опровержение тому, что код работает. Тест красный - поздравляю, у тебя появился шанс улучшить реализацию. Если не можешь найти очередной красный тест - вероятно ты сильно приблизился к завершению. Сделай перерыв и попобуй после. 

Намного проще (по классике) написать быстро функционал и сделать 3-4 теста, подтверждающих что все-все работает. Именно так bu default мыслит наш мозг, и тесты будут зеленые. Только не факт что они вообше что-то проверяют. А если и проверяют, то всего лишь верифицируют твою гипотезу о том, как должен работать код, а надо гипотезу фальсифицировать

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


С TDD Кент Бек привносит в кодирование научный подход. 

Бам!

суббота, 28 июля 2018 г.

Hackenjoy - как это было

В прошлой публикации я поделился анонсом мероприятия, который мы проводили  уже дважды. В этом будет отчет. 

Первым был offline ивент, когда я еще в GoIT развивал джаву в далеком 2015-08.

Вот коллажик из фоток


А вот более подробно о формате и заявленных игрушках ребят


В результате ребята получили бесценный опыт работы в парах (pair programming) а Codenjoy увидел около 15 новых игрушек. Не все их них были дописаны в тот день, но средняя готовность многих была около 90%. Реябтам судя по их отзывам очень понравилось. И я этому рад. 

В этом году, буквально две недели назад мы стартовали online hackenjoy на площадке Juja. Формат изначально заточенный под offline экспериментально попробовали провести в online. И получилось! Конечно выхлоп не такой, как в случае, когда мы заперли участников физически в одном помещении на двое суток и не выпускали без реализованной игрушки, но все же очень здорово. И да мне самому удалось написать игрушку, которая вскоре появится на demo сервере. 

Вот как это происходило в моем пространстве-время в трех частях.


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

Первая игра называется Quadro (GitHub).


Остальные в процессе...

суббота, 7 февраля 2015 г.

Онлайн вебинар по TDD



Что будет? Рассмотрим TDD на простеньких задачках. Как вести разработку так, чтобы быстро и надежно и без дебага. Я говорю и пишу код. Ты смотришь и задаешь вопросы.

Когда? Среда 11 февраля в 19:00 (если тема интересна, но время не подходит - зарегистрируйся и укажи этот факт - я вышлю материалы или договоримся о другой дате)

Где? В любом удобном для тебя месте с ноутом, наушниками и интернетом

Формат? Вебинар на 2 часа

Стоимость? 370 грн

Что потом? Следующий вебинар будет на тему TDD с legacy кодом

Регистрация Вот формочка, оставь свою анкетку и я с тобой свяжусь

четверг, 15 января 2015 г.

Тренинг "Test Driven development in Java" c GEEKS LAB в Одессе 31 января

Привет!

Тебя интересует карьерный рост как технаря.
Ты хочешь научиться разрабатывать свои приложения быстрее/качественнее, чем другие ребята.
Тебя задолбал стресс от того, что после фиксов постоянно что-то где-то вылазит.
Тебя заинтересует Test Driven Development.
Ты живешь в Одессе или можешь туда приехать.
Ты хочешь провести 31го января в кругу таких же как ты и потрейниться.
Ты на правильном пути! :)

(спасибо за рисунок, взято тут)

Детали по тренингу на этой страничке
Отчет об одном из прошлых тренингов по ТДД на этой страничке

Регистрируйся, приходи - будет весело!

пятница, 12 декабря 2014 г.

TDD c tetris codenjoy

Вот нашел в складках гугла :) Старое, наше с Серегой Зелениным выступление


Может кому пригодится :)

Что думаешь про TDD? Практикуешь? Хотел бы научиться?

ПриЁм

вторник, 22 июля 2014 г.

Мастеркласс по Test Driven Development

Вчера проходил мастеркласс по Test Driven Development во Львове. Планировался как мастеркласс, но в результате получилась дискуссия, что так же очень замечательно. Я кайфонул! Пока видео готовится, предложу твоему вниманию подборку ссылок на предыдущие посты в блоге, которыми поделюсь с участниками мастеркласса. Все на тему TDD и PP. 


А вот книги, которые помогут стать на рельсы эффективной разработки. Там и про ООП, и про юнит тестирование и про легаси код, и про Рефакторинг и про code quality и про шаблоны проектирования и про ТДД... Сборная солянка.
Вот видео ~10 часов разработки модельки игры змейки по TDD. Видео старенькое, кой-че я бы делал уже иначе, но в целом все остается как там. 
В чем разница - тесты до или тесты после? Собственно, в чем разница TDD или покрытие кода тестами. 
Как играть в TDD пинг-понг? Мы с напарником Сергеем на конференции java.io показываем как это, решать простенькую задачку в TDD Ping Pong 
Как вообще работать в паре (парное программирование)? Собрал сюда все мысли по этому поводу. Сидеть за одним компом вместе - это не парное программирование. Парное - это когда есть роли, правила и дисциплина. 
Что делать если тест изначально зеленый? Ага.. Лжет ли? И что вообще с тестами и их цветами. 
Ну и подробное описание нашего двухдневного тренинга по TDD. Тренинг-ликбез. Так, стать на рельсы. Магии не произойдет - ломать голову и привычки потом придется в любом случае. Но на тренинге получишь массу ответов на накопленные вопросы. 

Наскальные рисунки


А еще чуть позже (сегодня, пока я буду ехать в поезде) я закончу начатую работу и продемонстрирую, tdd в полную скорость до момента полной реализации.

Так что продолжение следует...

четверг, 30 января 2014 г.

Хотелось выпендрится

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

Пишу на ночь глядя эмулятор КР580ВМ80 - ну такое, just for fun. Конечно же по ТДД. Тесты рефакторю, как положено. И вот я в тесте пишу как-то так ассерт
    assertEquals("DEh", registry.m().toString());
При том, что
public class ProcessorTest {

    private Memory memory;
    private Processor processor;
    private Registry registry;

    @Before
    public void setup() {
        memory = mock(Memory.class);
        processor = new Processor(memory);
        registry = processor.registry();
    }
а интерфейс
public interface Registry {
    AByte b();
    AByte c();
    AByte d();
    AByte e();
    AByte h();
    AByte l();
    AByte m();
    AByte a();
}
а AByte содержит че-то умное и toString. Но мне хотелось выпендрится и сделать вот так в тесте, чтобы на at(). вываливались аутокомплитером IDE методы интерфейса Registry


Проверил, может ли такое Mockito. Оказалось может!
    private RegistryAssert assertValue(String data) {
        return new RegistryAssert(data);
    }
    
    class RegistryAssert {

        private String data;

        public RegistryAssert(String data) {
            this.data = data;
        }

        public Registry at() {
            // создаем шпиона для registry
            Registry spy = spy(registry); 
            // и следим за каждым его чихом
            setup(spy).b(); 
            setup(spy).c();
            setup(spy).d();
            setup(spy).e();
            setup(spy).h();
            setup(spy).l();
            setup(spy).m();
            setup(spy).a();
            return spy;
        }

        private Registry setup(Registry spy) {
            return doAnswer(new Answer<Object>() {
                @Override
                public Object answer(InvocationOnMock invocation) throws Throwable {
                    Object actual = invocation.callRealMethod();
                    assertEquals(data, actual.toString());
                    return actual;
                }
            }).when(spy);
        }
    }
Не делайте так ☺

суббота, 18 января 2014 г.

Готовимся к тренингу по ТДД

Почти все подготовительные моменты завершены. На следующей неделе стартует тренинг в одной уютной айтишной компании. Тренинг по Test Driven Development для специалистов С++. Это уже третий тренинг, который мы проводим с Сергеем не для java ребят (java - наш можно сказать привычный язык).

Что сделано?
  • Проведена встреча-знакомство с участниками тренинга (cпасибо SCRUMGuides, без Вас, ребята, мы бы не узнали друг про друга). 
  • Проведено портирование кода заданий, настройка фреймворков под С++ реалии (спасибо Виктор, без тебя никак!)
Что осталось?
  • Генеральная репитиция в следующую среду
  • Тренинг в конце недели
  • Коучинг на проекте через неделю после тренинга (с периодичностью, как решим)
  • Игра codenjoy немного спустя так, для fun
Для всех желающих познать TDD пару рекомендаций
Вообще магии быстро не случится. Ломать привычки все-равно прийдется. будет больно и неприятно. Будет очень много поводов спрыгнуть. Но если не сдаться, то вначале в голове случится test infected, а потом и вовсе test driven.

Сам процесс можно объяснить на пальцах за 15 минут. Все остальное, что приведет к росту продуктивности лежит в области "почему TDD в моем случае не работает и как сделать, чтобы работал" - тут нужны месяцы эксприментов.

Потому-то в XP и есть коуч в команде. Свойный, корпоративный коуч, который в команде пока она не станет на рельсы. Он каждый день играет в пару с каждым участником. Он знает основные грабли и то, как их по-приятнее получить. Без него ТДД в команде не будет.

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

И вот фоточка в тему - чем отличается обычный программист, от программиста практикующего TDD


среда, 24 апреля 2013 г.

TDD что он может?

На прошлом Bomberman Cdenjoy всплыла задачка makeBricks из codingbat.com. Вопрос был в том, как бы я решал эту задачу ТДД. Ну, начал бы с самого простого кейса, а там по ситуации. Лучший способ демонстрации - парное программирование, потому мы с Виталиком сели в пару за комп и начали. Первый наш код получился таким.
public class Main {
    @Test
    public void test1() {
        assertBricks(true, 0, 1, 5);
        assertBricks(true, 1, 0, 1);
        assertBricks(false, 1, 0, 2);
    }

    public boolean makeBricks(int small, int big, int goal) {
        return small + big * 5 == goal;
    }

    private void assertBricks(boolean expected, int c1, int c5, int l) {
        boolean actual = makeBricks(c1, c5, l);
        assertEquals(String.format("C1 = %s, C5 = %s, L = %s, Actual = %s, But expected = True", c1, c5, l, actual, expected),
                expected, actual);
    }
}
Но уже после 4го теста (ассерта) мы пришли к тому, что начали путаться... И я предложил выйти в тестах на более высокий уровень абстракции. Код преобразился и стал понятнее описывать доменную область.
public class Main {
    private static final int HHHHH = 5;
    private static final int H = 1;
    public static final boolean NO = false;
    public static final boolean YES = true;

    @Test
    public void test1() {
        YES(5, HHHHH);
        YES(1, H);
        NO(2, H);
    }

    public boolean makeBricks(int small, int big, int goal) {
        return small + big * 5 >= goal;
    }


    private void YES(int l, int... bricks) {
        assertBricks(YES, l, bricks);
    }

    private void NO(int l, int... bricks) {
        assertBricks(NO, l, bricks);
    }

    private void assertBricks(boolean expected, int l, int... bricks) {
        int c1 = 0;
        int c5 = 0;
        for (int i : bricks) {
            if (i == H) {
                c1++;
            }
            if (i == HHHHH) {
                c5++;
            }
        }
        assertL(expected, c1, c5, l);
    }

    private void assertBricks(boolean expected, int c1, int c5, int l) {
        boolean actual = makeBricks(c1, c5, l);
        assertEquals(String.format("C1 = %s, C5 = %s, L = %s, Actual = %s, But expected = True", c1, c5, l, actual, expected),
                expected, actual);
    }
}
Работа пошла быстрее! В следующие пару минут мы написали еще пачку тестов
    @Test
    public void test1() {
        YES(5, HHHHH);
        YES(1, H);
        NO(2, H);
        YES(7, HHHHH, H, H, H);
        YES(14, HHHHH, HHHHH, H, H, H, H);
        NO(14, HHHHH, HHHHH,  H, H, H);
        YES(5, HHHHH, HHHHH);
        YES(6, HHHHH, H, H, H);
        NO(9, HHHHH, H, H, H);
    }
Реализация совсем немного изменилась, чтобы соответствовать тестам.
    public boolean makeBricks(int small, int big, int goal) {
        return small + big * 5 >= goal;
    }
И тут началось самое интересное. Мы больше не могли добавить ни одного красного теста. И так и сяк. Зависли. Но в то же время http://codingbat.com/prob/p183562 сказал нам, что мы забыли как минимум один кейс.
    @Test
    public void test2() {
        NO(9, HHHHH, HHHHH, H, H, H); 
    }
И тут возник заслуженный вопрос. Какова роль ТДД тут? Почему он не помогает решить эту задачу - найти еще один красный кейс? Ответ был таким - ТДД помогает убедиться в том что, что все кейсы которые ты придумал работают (более вероятно, чем если бы ты писал без тестов). ТДД помагает в поиске новых кейсов только тем, что сразу же сигнализирует о твоих ошибках, что со временем приводит к осознанию, что "я ошибаюсь часто", "я что-то недопроверил" - а этот скепсис по направлению к своему коду, помогает задуматься над поиском дополнительных кейсов. А вот как их искать, нам подсказал уже Лёня. Комментарий Виталика был такой (и я с ним полностью согласен)
Цікаво шо ці всі викрутаси можна почати тільки від одного "чувстуетсья что что-то не то", в тому випадку у нас були тести які явно показали, якшо їх нема, то може або провтикатись, або "почувствоваться", це з досвідом приходить, професійна подозрітєльность на такі штуки. Да і рішення в тих варіантах які запропоновано в основному інтуітивні, або направлені на то шоб допомогти інтуіції прийти до червоного кейса.
Только вот хотелось бы какой-то более конкретный математический аппарат для поиска красных кейсов. Уверен, что опытные тестировщики уладеют такими инструментами. На этом пока все. Пока буду искать ответ. Как появится - опишу его тут.... Спасибо Леня, Виталик! P.S. Вот та причина, почему я люблю блог. Описал в нем проблему, отложил ее в сторону и мозг твой сгенерил решение. Мой мне напомнил про то, что есть такая чудная библиотека, которая называется Approvals (кстати в прошлом месяце вышла новая версия). Что я с ней сделал? Ну кроме того, что скачал ее отсюда. А потом прописал maven dependency
<dependency>
 <groupId>net.sourceforge.approvaltests</groupId>
 <artifactId>approvaltests</artifactId>
 <version>0.1.3</version>
 <scope>system</scope>
 <systemPath>${project.basedir}/lib/ApprovalTests-0.1.3/ApprovalTests.jar</systemPath>
</dependency>
Я написал вот такой вот несложный тест
import org.approvaltests.legacycode.LegacyApprovals;
import org.approvaltests.legacycode.Range;
...
public class Main {
...
    @Test
    public void testApprovals() throws Exception {
        LegacyApprovals.LockDown(this, "approveMakeBricks", Range.get(0, 10), Range.get(0, 10), Range.get(0, 100));
    }

    public boolean approveMakeBricks(Integer small, Integer big, Integer goal) {
        return makeBricks(small, big, goal);
    }
Запустив его я увидел что возвращает метод во всех комбинациях входных параметров. Я стал критиковать эти результаты, и уже на 133 строчке (через минуту беглого просмотра) я первую увидел неточность.
[0, 0, 0] = true 
[1, 0, 0] = true 
[2, 0, 0] = true 
[3, 0, 0] = true 
[4, 0, 0] = true 
[5, 0, 0] = true 
[6, 0, 0] = true 
...
[6, 10, 0] = true 
[7, 10, 0] = true 
[8, 10, 0] = true 
[9, 10, 0] = true 
[10, 10, 0] = true // Все что выше всегда тру, это и ежу понятно - длину 0 мы составим из элементов любого набора
[0, 0, 1] = false // тут ок потому, как материала не хватает 
[1, 0, 1] = true // все что ниже тоже ок, потому как имея хоть одну H мы сможем составить длину 1 всегда 
[2, 0, 1] = true 
[3, 0, 1] = true 
[4, 0, 1] = true 
[5, 0, 1] = true 
[6, 0, 1] = true 
[7, 0, 1] = true 
[8, 0, 1] = true 
[9, 0, 1] = true 
[10, 0, 1] = true 
[0, 1, 1] = true // а вот тут интересно! Мой алгоритм считает, что можно взять HHHHH и c нее составить L=1, что не ок. 
[1, 1, 1] = true 
...
Вот вам и красный тест
    @Test
    public void test2() {
        NO(1, HHHHH); 
    }
Главная фишка аппрувалса - пусть каждый делает то, что ему свойственно: комп пусть считает комбинации, а человек пусть оценивает правильность результата. Решилось это как-то так
    public boolean makeBricks(int small, int big, int goal) {
        if (goal == 0) {
            return true;
        }
        if (small == 0) {
            return goal % 5 == 0 && goal / 5 <= big;
        }
        return small + big * 5 >= goal;
    }
И главное теперь тест написанный на аппрувалсе тоже стал говорить!
 Поехали дальше...
...
[0, 0, 2] = false // не хватает материала
[1, 0, 2] = false // не хватает материала
[2, 0, 2] = true  // тут и дальше все хватает
[3, 0, 2] = true 
[4, 0, 2] = true 
[5, 0, 2] = true 
[6, 0, 2] = true 
[7, 0, 2] = true 
[8, 0, 2] = true 
[9, 0, 2] = true 
[10, 0, 2] = true 
[0, 1, 2] = false // снова не хватает материала
[1, 1, 2] = true  // а тут интересно! Опять пытаемся поделить пятерку чтобы сделать из нее что-то... Прошлый фикс не совершенен!
[2, 1, 2] = true 
[3, 1, 2] = true 
[4, 1, 2] = true  
...
Итого красный тест таков. Я сразу добавил однотипные кейсы, потому что чувствую мощь!
    @Test
    public void test3() {
        NO(1, HHHHH);
        NO(2, HHHHH, H);
        NO(3, HHHHH, H, H);
        NO(4, HHHHH, H, H, H);
    }
Получилось как-то так
    public boolean makeBricks(int small, int big, int goal) {
        if (goal == 0) {
            return true;
        }
        return (goal - small) / 5 <= big && (goal - small) % 5 == 0 || (goal - big*5) <= small && goal >= big*5;
    }
Не спрашивай как я пришел к этому. Просто как-то уивделось такое решение. Кроме того оно не оптимальное, так как я поломал старое (то что засек approvals но не засекли мои тестики) потому я решил добавить в свой набор тестов этот кейс.
    @Test
    public void test4() {
        YES(1, HHHHH, H, H);
    }
Этот тест проверяет, что у меня и больших и маленьких в избытке.
    public boolean makeBricks(int small, int big, int goal) {
        if (goal == 0) {
            return true;
        }
        return (goal - small) / 5 <= big && (goal - small) % 5 == 0 
                || (goal - big*5) <= small && goal >= big*5 
                || goal < small; // фикс простой как двери
    }
А как на счет этого теста?
    YES(6, HHHHH, HHHHH, H, H);
Потом мне приспичило в туалет. А возвращался я уже с этой идеей
    public boolean makeBricks(int small, int big, int goal) {
        return goal / 5 <= big && goal % 5 <= small;
    }
И как я до этого сразу не додумался? Но это еще не все. Просмотр таблицы результатов остановила меня возле теста
    YES(11, HHHHH, H, H, H, H, H, H);
Блин! Так все идеально было :) Решение вот
    public boolean makeBricks(int small, int big, int goal) {
        return goal / 5 <= (big + small / 5) && goal % 5 <= small % 5;
    }
Но тест (судя по изменениям - я уже не так просматриваю таблицу, как слежу за diff)
 Напишем такой тестик
    YES(1, H, H, H, H, H);
Нате! Родил
    public boolean makeBricks(int small, int big, int goal) {
        return goal / 5 <= (big + small / 5) && goal % 5 <= ((((goal / 5 - big - small / 5) == 0) && (small / 5 != 0)) ? (small % 5) : small);
    }
Ну и задачка :) Я че сюда полез, потому что мне сказали что ее можно решить без циклов :) Я не нашел проблем в approvals тесте - обратился к codingbat.com и там тоже получил аппрув
 Теперь осталось заапрувить все в аппрувалс тесте и провести рефакторинг....
      public boolean makeBricks(int small, int big, int goal) {
        int needBig = goal / 5;
        int needSmall = goal % 5;
        int smallAsBig = small / 5;
        int smallWithoutBigGroups = small % 5;
        int stillNeedBig = needBig - big - smallAsBig;

        boolean usedSmallAsBig = stillNeedBig == 0 && smallAsBig != 0;

        boolean enoughSmall = needSmall <= (usedSmallAsBig ? smallWithoutBigGroups : small);
        boolean enoughBig = stillNeedBig <= 0;

        return enoughBig && enoughSmall;
    }
Как-то так...

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

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

воскресенье, 10 марта 2013 г.

JUnit хитрости: Ассертим все и сразу

Вот как часто пишут пользовательсвие ассерты.
    @Test
    public void test() {
        snake = new Snake(50, 50);

        assertHeadAntTail(50, 50, Direction.RIGHT, Direction.LEFT);
    }

    private void assertHeadAntTail(int x, int y, Direction headDirection, Direction tailDirection) {
        assertEquals(headDirection, snake.getDirection());
        assertEquals(tailDirection, snake.getTailDirection());
        assertEquals(x, snake.getHead().getX());
        assertEquals(y, snake.getHead().getY());
    }
Их недостаток в том, что если слетает первый, то мы не увидим всей картины - остальные не отработают. Этот антипаттерн юнит-тестирования называется "Заяц" - обычно чтобы не писать несколько разных тестов, мы просто подселяем к существующему тесту еще один ассерт. Кстати тут пахнет даже имя метода - assertHeadAndTail :) но проигнорим на минутку этот запах.
Можно изворачиваться так, как я это делал раньше. Но сегодня придумался другой способ, более легковестный.
    @Test
    public void test() {
        snake = new Snake(50, 50);

        assertHeadAntTail(50, 50, Direction.RIGHT, Direction.LEFT);
    }

    private void assertHeadAntTail(int x, int y, Direction headDirection, Direction tailDirection) {
        assertEquals( "[headX, headY, headDirection, tailDirection]",
                asString(x, y, headDirection, tailDirection),

                asString(snake.getHead().getX(), snake.getHead().getY(),
                        snake.getDirection(), snake.getTailDirection()));
    }

    private String asString(Object...args) {
        return Arrays.asList(args).toString();
    }
Слетает так
junit.framework.ComparisonFailure: [headX, headY, headDirection, tailDirection] 
Expected :[50, 50, RIGHT, LEFT]
Actual   :[50, 50, RIGHT, DOWN]
 at com.codenjoy.dojo.snake.model.SnakeDirectionTest.assertHeadAntTail(SnakeDirectionTest.java:72)
 at com.codenjoy.dojo.snake.model.SnakeDirectionTest.test(SnakeDirectionTest.java:36)
Меня устраивает!
Вот если бы только можно было бы находясь в методе получить список его параметров в текстовом виде, чтобы не писать вот так "[headX, headY, headDirection, tailDirection]" в каждом новом ассерте - было бы вообще идеально. Но эту головоломку остави на потом...

вторник, 19 февраля 2013 г.

TDD тренинг из угла комнаты

Начну с термина "Training from the back of the room". С ним мы с Сергеем познакомились в прошлом году. До этого наши тренинги были больше похожими на семинары (пример такого семинара в КПИ), на которых мы рассказывали ребятам, что такое ТДД, давали практическое задание и помогали с его реализацией на новом инструменте. 

Мы много говорили и, как позже стало нам понятно - тренинг это не семинар. Это на семинаре/вебинаре можно говорить, а на тренинге стоит поднимать тяжести и делать это практически все время, отведенное под тренинг. Опытные тренера (Максим Роменский и Ирина Синчалова) в разное время говорили нам, что тренинг - это 70% работы и всего 30% разговоров... И именно в этом порядке, не иначе (об этом уже нам поведал Слава Панкратов, рассказывая про цикл Колба на одном из своих мастерклассов). 

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

Стоит менять подход. И тут у нас в руках появляется инструмент Coding Dojo. Его привозит к нам Johannes Brodwall. В тот день Сергею удалось покодить с Johannes в паре решая задачку Primes Factor. Кроме интересной Primes Factor мы получили на руки инструмент Coding Dojo и так же провели его в GlobalLogic. Это был успех! О нем свидетельствовали 100% позитивный фидбек участников. Собака зарыта тут! И мы стали копать. Вернее разрабатывать...

Наши Automated Testing Dojo для автоматизаторов-тестироващиков (спасибо Мише Полярушу и Андрею Дзыне за поддержку, а так же Глебу Рыбалко и  Вике Мусияченко за предложение "а придумайте чего-нибудь для автоматизаторов на конфу"), а потом и Tetris Coding Dojo для программистов, доказали нам что мы роем в правильном направлении.

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

В ходе проведения dojo с Сергеем мы были как бы "в углу команты", наблюдая за тренингом который по факту шел сам. Будучи при этом на подхвате, чтобы в любой момент дать ответ на вопрос участников. А еще лучше переадресовать вопрос остальным участникам. И никакой критики! Эксперимент однозначно приветствуется, когда видишь очки игроков на доске и свое место в leader booard. Попробовал свое -> посмотрел, как оно -> если не ок -> попробовал новое -> посмотрел, как оно -> принял решение оставлять либо дальше экспериментировать... Колб был бы доволен...


Но это касается развлекательных мероприятий. Тренинг же, особенно, когда ребята платят за него деньги, требует более детальной проработки. Кроме того фидбеки предыдущих тренингов говорят о том, что стоит изменить. Да и новый опыт в Dojo говорит о том же. Так получается, что наш новый тренинг практически никогда не похож на предыдущий - всегда что-то новое, всегда более эффективное (ибо проверено).


Так было и в этот раз. Мы с Сергеем полностью переделали наш тренинг с нуля. Включили в него только то, что необходимо и исключили из него все лишнее. И на этот раз у нас вот что получилось: 
- мы говорили мало (в пределах 30%), 
- ребятам было чем занять свой мозг (мы выбрали лучшие игрушки, в которые за год игрались сами), 
- ребятам это было полезно, поскольку основная тема - тренинг TDD под присмотром коучей.
- это было эволюционно - юнит тестирование -> TDD + Test List -> TDD в legacy -> TDD и Refactoring -> TDD и SOLID (SRP, OCP, (IoC) DIP) -> Unit Testing с Mock.

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

Тренинг начался с разогрева. Ибо рано утром все мы очень инертны, чтобы сразу думать. Сергеем понравилcя инструмент "Миф-Реальность". Суть его вкратце в том что есть карточки, на них написано некое утверждение. И вот заходит первый участник, открывает свой ноут (почту проверить), а мы ему даем карточки и просим разложить что по его мнению миф, а что реальность. Следующий участник тренинга приходит и видит, как уже что-то происходит и тут же включается. Это нам пообещали наши тренера, и так оно оказалось на практике. Не занятое проблемой сознание скучает и ищет себе развлечения. Мы имели с этим немало хлопот на первых своих тренингах. Сейчас же знаем - начинать стоит с включения лампочки в голове. Задание "Миф-реальность" очень хорошо для этого подходит. Вот примеры утверждений, написанных на карточках. 
  • На написание тестов уходит слишком много времени   
  • Тесты отвлекают от размышлений над логикой
  • Все и так работает   
  • Тестировать слишком сложно
  • Заказчик никогда не согласится на полноценное ТДД в команде   
  • Рефакторить без тестов опасно
  • Код меняется редко   
  • В ТДД можно использовать дебаггер
  • С ТДД приходится больше общаться в команде   
  • С ТДД овертаймишь больше
  • С ТДД нет ручного тестирования   
  • Тесты не придется переписывать
  • ТДД позволяет получить мгновенный фидбек от системы   
  • ТДД приводит к 100% степени покрытию кода тестами
  • ТДД избавит нас от всех проблем   
  • С ТДД не надо проектировать архитектуру
А по результатам (голосование) можно судить о настроении группы касательно предложенной темы. Конечно же этого недостаточно, а потому мы опросили ребят еще до начала тренинга. Нас интересовал существующий опыт ребят и их вопросы. Это позволило нам оттюнить тренинг под ребят, а вопросы мы выписали на флипчарт и позже, в конце дня возвращались к нему. Это был наш тестлист.... 
  • Как эффективно использовать TDD, чтобы не тормозилась разработка? Какие выгоды даст TDD?
  • Как работать в парном программировании, не мешая?
  • Как правильно выбирать инструменты тестирования в зависимости от решаемой проблемы: EasyMock, PowerMock, Mokito ... ?
  • Необходимо ли использовать TDD во всех случаях проектирования/кодирования?
  • Насколько TDD повышает качество кода?
  • Реально ли использовать TDD с незнакомым фреймворком/чужим кодом?
  • С чего начать?
  • В каких случаях лучше использовать TDD, а в каких его использование нецелесообразно?"
  • Есть ли смысл покрывать тестами незнакомую логику на начальном этапе расширения функционала проекта?
  • Как сделать, чтобы рефакторинг кода требовал минимальное изменения кода тестов?
  • Есть ли смысл в UNIT-тестах для делегирующего функционала? 
  • Как писать тесты для легаси кода?
  • Как тестить класс, нарушающий Single Responsibility Principle?
А опрсник перед тренингом были такой:
  • Пробовал ли использовать TDD в реальных проектах? (опиши пару предложений как это было, что не получалось, какие вопросы остались открытыми)
  • Какие вопросы ты хотел бы нам задать? (чем больше - тем лучше)
  • Зачем тебе этот тренинг? (как узнал про тренинг, что будешь делать после него, ... )
Чем понравилась группа, так это тем, что ребята хотели понять - зачем им TDD, как он им может пригодится для решения каждодневных задач. Это чувствовалось на протяжении всего тренинга. Ребята пришли за знаияниями. И нет ничего лучше для тренера. Я помню вечера обоих дней тренинга - я шел домой пешком, любовался видами вокруг - у меня в голове не было ни одной мысли, все вычистили за день :) Домой я пришел, душ, сёрбнул поесть,  свалился с ног на диван и тут же заснул. Усталость может быть приятной.

А утром в 6:00 как штык править тренинг, ведь первый день внес свои коррективы. И с удовольствием. Потому что не зря идешь...

Что-то отвлекся я. Итак у нас первый день тренинга 10:15. Ребята в сборе. Мы познакомились. За "миф-реальность" проголосовали, вопросы выписали и второе задание в студию! Игра FizzBuzz (спасибо Диме Миндре). Мы на тренинге для тренеров с Сергеем играли в похожую игру - 33 называется. Правила простые. Играют стоя. Говорят по очереди число от 1 до 33. Если твое число делится на 3 либо содержит 3ку ты должен хлопнуть в ладоши, иначе сказать число. Ребята обычно сбиваются и тот кто налажал - должен начать с начала. Весело. Вот и мы решили ее повторить, но в несколько усложненной форме, называемой FizzBuzz.

Итак групповая игра. Счет начинается с 1. Каждый следующий по кругу участник работает с числом на 1 больше, чем у коллеги: он говорит «физ» если его число делится на 3 без остатка, говорит «баз» если его число делится на 5, говорит «физбаз» если его число делится и на 3 и на 5 одновременно, либо говорит само число.
Если кто-то из участников сбивается, то он начинает игру с начала. Игра окончена, если команда дошла до 33 не сбиваясь со счета.

К реализации. А теперь найдите напарника и напишите вместе по Test Driven Development метод, который подскажет вам, что надо сказать в тот или иной момент. Используйте TDD так, как вы умеете. Вот сигнатура метода: 

Template рабочего проекта можно получить на общем svn repo, которое мы подняли на одном из наших ноутов. Это очень удобно, поскольку в любой момент времени мы как тренера могли иметь доступ к исходному коду любой пары, вносить в него изменения, изучать. Так же ко всему коду имели доступ и другие ребята, и хоть мы условно разделили репозиторий на 6 одинаковых папок (для каждой пары), это не помешало нам всем перемиксовать пары на второй день.


В качестве репозитория был выбран VisualSVN Server из за своей простоты. Поднялся на раз два. Это было удобнее, чем все наши предыдущие попытки. С самого начала дня IP компа с полным путем к реппозиторию был выписан на флипчарте. Ребятам сообщили, что там они могут скачать проект своей пары. Номер пары был написан на стике, который был приклеен к столу. Так и мы и пара знала какой у нее номер. 

Кстати да - вся работа проводилась все два дня в парах. Мы параллельно с ТДД рассматривали (да нет же - трейнились) правила игры в pair programming. Было много откровений, так же и у нас. Например лично для меня было новостью, когда ребята работали за двумя ноутами но в паре, просто ноуты очень разные все и сложно привыкнуть к разным калвиатурам. А тут вначале ты смотришь в мой ноут, а потом я в твой - SVN репозиторий жеж один. Супер! люблю уносить из тренинга что-то новое для себя.. 

Про парное программирование мы сказали (вернее написали на раздатках) всего ничего. Что есть две роли. Говорит тот, у кого клавиатура – пилот – он рассказывает то, что сейчас реализует. Напарник – штурман – не критикует, а следит за «очепятками» пилота. Клавиатурой (и ролями) меняются, когда текущий шаг сделан, либо когда пилот «сдается» (не может решить текущий шаг за 5 минут). 

В процессе сегодняшнего дня мы еще неоднократно цепляли тему правил парного программирования - что должен, а что не должен делать человек находящийся в той или иной роли. Например пилот не должен отвлекаться, но если он отвлекся, то шутрман должен выписать его идею в тестлист. Вторую роль весь день называли "хранителем тестлиста", а вопрос "где ваш тестлист" наверное прозвучал из наших уст раз 30 не меньше. Вначале мы с Сергеем боролись с привычкой ребят все держать в голове, но что порадовало, так это то, что на следующее утро ребята уже сами вели тестлисты к своим заданиям и мы на это больше не отвлекались...Супер! Цель тренинга развить новые привычки, и похоже с test list это получилось.


Test list сам по себе полезный инструмент. Просто очень удобно не держать все в голове, а в парном программировании штурмана надо было как-то еще нагрузить - не только же ошибки искать будет. Вот и стал он хранителем тестлиста. И цель его не болтать, а записывать. Болтать он вообще не должен ничего, даже если он Senior по сравнению с нубчиком пилотом. Да он знает, что так писать нельзя, но пока он штурман, он должен дать возможность пилоту закрешиться. Только получив больно граблями по голове пилот отдает клавиатуру и внимательно начинает слушать. Если же его перебить посреди его реализации, он скорее всего в мыслях продолжит кодить и слушать опытного не будет. 


Но тут штурман не должен давать много свободы пилоту. Еще одна его роль - следить за временем на реализацию текущего теста из тест листа. 5-10-15 минут не больше. После этого времени штурман должен настоять на том, чтобы пилот признал неудачу и отдал ему клавиатуру. Штурман - хронограф пары, он следит з временем.... 

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

Ну а чтобы поднять боевой дух, мы просили ребят на каждой зеленой полосе "давать пять" друг другу.  Эта идея так же была позаимствована с мастер-класса Димы Миндры. 

Идея "High five" доработана идеей Сергея раздавать монетки-шоколадки за каждый такой хлопок ладошками. А раздавать было что.


Всякий раз, когда вы получаете зеленую полосу, «дайте пять» своему напарнику – вы только что продвинулись чуть ближе к финишу – это стоит отметить. Каждый ваш «high five» мы будем  поощрять шоколадными медалями

Но раздавать конфетки-монетки было так приятно, что мы стали их раздавать за хорошие вопросы, когда слышали в диалоге пары правильные с точки зрения ТДД мысли.... Конфетки расходились очень быстро. Кто-то их ел. Кто-то собирал. В любом случае конфет после тренинга осталось и моя дочь передала "больше спасибо дядям, которые не съели все ее конфеты"

Решение задачи FizzBuzz было не сложным. Но мы хотели, чтобы ребята попробовали реализовать его по TDD как умеют, так как они его (TDD) понимают. И стало понятно - перед нами группа подготовленная, как минимум юнит тестирование регулярно используют. И это замечательно! А решение было где-то таким:

package com.codenjoy.tdd;
import static junit.framework.Assert.assertEquals;
public class FizzBuzzSolver {
    public static final String FIZZ = "fizz";
    public static final String BUZZ = "buzz";

    public static String get(int num) {
        validate(num);
        String result = null;

        if (num % 3 == 0 && num % 5 == 0) {
            result = FIZZ +"&"+ BUZZ;
        } else if (num % 3 == 0) {
            result = FIZZ;
        } else if (num % 5 == 0) {
            result = BUZZ;
        } else {
            result = String.valueOf(num);
        }
        return result;
    }

    private static void validate(int num) {
        if (num<1 || num>33) {
            throw new IllegalArgumentException("Argument should be in [1-33].");
        }
    }
}
И валидация и выглядит симпотичненько. Но нас с Сергеем как тренеров интересовало не так решенное задание, как ответ на вопрос:
- как проходит работа в парах
- как ребята понимают TDD (а именно мантру Красный Зеленый Рефакторинг) 
- как проходит работа с тест листом

Второе задание было несколько сложнее, называлось оно PrimesFactor. Цель задания показать, каким простым должен быть каждый шаг. Мы даже в раздатке указали подсказки, но кажется туда никто не смотрел - ребятам было интересно все реализовать самим.

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

Под конец этой игры мы с Сергеем записали свою реализацию, чтобы продемонстрировать ребятам, как это происходит, когда "рука набита". Дело в том, что ТДД практики обещают ускорение разработки, и это понятно - как минимум, в ТДД исключен самый трудоемкий процесс разработки - отладка. Но из-за тяжеловесности дисциплины ТДД поначалу время на разработку простых кейсов может достигать 5x, что неприемлемо. Чтобы не остался неприятный фидбек "что это долго" мы показали ребятам это 8-минутное видео...


Итого что имеем? Привычки ломать сложно, коуча в команде нет (а в XP предусмотрен персональный коуч, как в SCRUM предусмотрен ScrumMaster), прироста производительности не наблюдается.... Поводов спрыгнуть с ТДД очень много, да и этот Legacy проект, который еще непонятно как протестировать.... В общем, ТДД хорошо бы изучать еще в вузе, вместе с программированием...


Я лично спрыгивал три раза за пол года, пока не пришла привычка и я не стал видеть прирост эффективности. У Сергея становление ТДД случилось быстрее на пару месяцев. Сейчас же мы можем использовать как ТДД так и классику со своим DEBUG, и выбираем в зависимости от случая. Иногда и подебажить быстрее - а значит не грех. ТДД всего лишь инструмент, который где-то хорош, где-то не очень. Об этом мы и говорили все два дня. Цель тренинга как раз получить новый инструмент в свой арсенал разработчика, или даже не так - как можно больше времени провести с инструментом под присмотром опытных (слава Богу) коучей. А потом попробовать еще раз самостоятельно. Вообще идеально, если на своем проекте комнада будет иметь коуча 40 часов в неделю. Но такого числа специалистов практикующих ТДД достаточно, чтобы отвечать на хитрые вопросы команды и убеждать через парное программирование на самом деле не так уж и много.... А жаль...


Итак первая половина подошла к концу. И мы все вместе мигрировали в общепит.


Покормили нас вкусно и за счет тренинга. После обеда мы минут двадцать ничегонеделали :) Но потом начался Tetris coding dojo, но не обычный как мы в него неконтролируемо играем на соревнованиях среди проггеров. Сейчас мы вооружившись тестлистами и ТДД стали писать непривычно. 

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

Так первый день подошел к своему концу. Мы обсудили вопросы, которые выписали с утра



И отправились по домам.

Второй день был достаточно хардкорным. Заданий было всего три, но какие. Первое задание - LegacyCaclulator. Это один класс, который умеет суммировать x-ричные числа. Он запутанный. Он без тестов. Он ужасен. Задача - реализовать на его основе сумматор римских чисел.

Первая ретроспектива показала, что ребята пошли разными путями. Кто-то взялся покрывать тестами код легаси калькулятора и рефакторить его, кто-то решил не трогать старый код а повторно реюзать его для чисел в 10-чной системе, предварительно перегнав их из римской, а после суммирования результат обратно в римскую систему. Умно! Хитро! Молодцы! Но мы попросили все же оставлять после себя систему чище и прозрачнее, чем она была до того как мы открыли код. Принцип скаута. 


Важным свойством legacy code является его рабочее состояние. Не важно, кем и как он написан – главное то, что он отлажен предшественниками. А значит перед тем, как мы будем вносить в него изменения, стоит добиться максимально возможного покрытия тестами. Так мы не будем волноваться, что что-то можем поломать.

Но мы не знаем, как код работает! Как писать test case? Воспользуемся brood force. Вот тест, написанный с помощью Approval Tests Library


Вот результат его выполнения проверенный Code Coverage tool (под Eclipse это EclEmma Plugin в IDEA Ultimate – Emma Plugin)



А в сырцах видно, какие строки не покрыты. Фидбек есть, а значит тест можно улучшить!


Ребятам библиотека понравилась. Следующая ретроспектива выявила ребят, которые решили разделять зависимости парсинга от калькуляции. Это и было наше следующее предложение ребятам.

Дело в том, что калькуляция чисел и парсинг их из x-ричного (а так же любого другого, например Римского) представления – две разные ответственности. А значит, если мы разместим их в одном классе – мы нарушим Single Responsibility Principle OOP. Суть его в том, что класс создается с одной и единственной целью и если в ходе его расширения появляется намек на новую роль – ее стоит выделить в отдельный метод, а потом и класс.


Вслед за этим стоит реализовать Inversion of Control принцип, который гласит
  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Одна из реализаций IoC принципа ООP – Dependency Injection. Суть ее лучше передаст Википедия

Внедрение зависимости (англ. Dependency injection) — процесс предоставления внешней зависимости программному компоненту. Является специфичной формой «обращения контроля (англ. Inversion of control)», где изменение порядка связи является путём получения необходимой зависимости. Условно, если объекту нужно получить доступ к определенному сервису, объект берет на себя ответственность за доступ к этому сервису: он или получает прямую ссылку на местонахождение сервиса, или обращается к известному «сервис-локатору» и запрашивает ссылку на реализацию определенного типа сервиса. Используя же внедрение зависимости, объект просто предоставляет свойство, которое в состоянии хранить ссылку на нужный тип сервиса; и когда объект создается, ссылка на реализацию нужного типа сервиса автоматически вставляется в это свойство (поле), используя средства среды. Внедрение зависимости более гибко, потому что становится легче создавать альтернативные реализации данного типа сервиса, а потом указывать, какая именно реализация должна быть использована в, например, конфигурационном файле, без изменений в объектах, которые этот сервис используют. Это особенно полезно в юнит-тестировании, потому что вставить реализацию «заглушки» сервиса в тестируемый объект очень просто. С другой стороны, излишнее использование внедрения зависимостей может сделать приложения более сложными и трудными в сопровождении: так как для понимания поведения программы программисту необходимо смотреть не только в исходный код, а еще и в конфигурацию, а конфигурация, как правило, невидима для IDE, которые поддерживают анализ ссылок и рефакторинг, если явно не указана поддержка фреймворков с внедрениями зависимостей.
Самой простой реализацией DI – является инъекция зависимости в зависимый класс через конструктор (в случае композиции), либо чрез set-метод (в случае более общей агрегации). В так на сцене появляется некий Class3, который проводит эту инъекцию, а потом делегирует выполнение Class1, который, в свою очередь, пользуется своей зависимостью Class2 под видом Interface2. Магия в том, что в Class1 можно вставить и другую реализацию Interface2 в том числе и Mock (а так же Stub, Fake, DummyObject, Spy, …) для целей тестирования.

После того, как абстракция и зависимость будут выделены – реализацию абстракции можно протестировать отдельно как unit (либо разработать по TDD). Так же можно протестировать и зависимый класс отдельно от зависимости, используя при этом Mock framework. Мы рекомендуем Mockito.



Замечательно то, что вы можете вести разработку зависимого Class1 с нуля (по TDD) при этом не имея на руках реализации зависимости (Class2), имея на руках всего лишь Абстракцию (Interface2). Реализацию зависимости (Class2) можно разработать позже, после того, как зависимый Class1 будет готов. Далее модули (Class1 и Class2) интегрируются и эту интеграцию так же стоит протестировать, ведь наличие одних только unit-тестов не гарантирует работоспособность системы.

Предложили попробовать разделить зависимости Калькуляции от Парсинга+Валидации и протестировать их отдельно (используя Mockito для Class1). Если задача (завоевания Рима) еще не решена, разработку (по ТДД) рекомендовали продолжать в русле шаблона Strategy.

А Рим так просто не сдавался. Причем мы с Сергеем явно недооценили эту задачу. Она съела наш мозг! Так продолжалось до самого вечера. Официального закрытия тренинга не было и это сделано было специально - дело в том, что закрывать рано. Все только начинается....

И потому, ребята, вот мой скайп - alexander.baglay - пишите с вопросами, будем рады помочь.

Обещанные книги для TDD практиков

Вся информация по Tetris Coding Dojo

Добавляйтесь к нам в Facebook-группу чтобы следить за новостями

А посмотреть, какие мы были красивые можно тут

Отдельное спасибо Саше из DIO Soft за помощь в организации конференца.


А так же Лине (ScrumGuides) за помощь в сборе группы и нарезании карточек Мир-Реальность.


Сережа, спасибо и тебе за то что ты такой клевый Напарник!


Продолжение следует!