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


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

вторник, 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) за помощь в сборе группы и нарезании карточек Мир-Реальность.


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


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

Комментариев нет:

Отправить комментарий