Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts

Wednesday, February 12, 2014

Прикольный фреймворк для тестов Spock.

Недавно на проекте заюзали spock для юнит тестов. Ему, оказывается, уже много лет (целых 4 года!), а я узнал о нем только сейчас. Так обидно стало за бесцельно прожитые годы рядом с морально устаревшим JUnit'ом :).

Кто такой Спок?

Спок - коммандер и научный сотрудник звездолета "Энтерпрайз", родился в 2230 году по земному летоисчислению. Наполовину землянин, наполовину вулканец. Увлечение компютерами привело его в звездный флот, где он и сделал свою карьеру научного сотрудника.
Кому не хватило терпения посмотреть сериал, на википедии есть краткая биография Спока.
Не знаю, что сподвигло разработчиков назвать фреймворк для тестирования именем персонажа из Стартрека. Может двойственная натура героя (spock хорошо подходит как для тестирования Java так и Groovy кода), а может что еще.

Wednesday, October 24, 2012

Unit тесты с embedded Jetty

Частенько хочется проверить обращение к внешнему HTTP серверу. Раньше я пользовался встроенным в JDK http сервером (см пакет com.sun.net.httpserver). В этом случае тест работает быстрее, но вытаскивать параметры из http запроса приходилось писать самому.

Я попробовал запустить embedded Jetty сервер в тесте, в котором создать сервлетик, который запоминает приходящие параметры реквеста. Вот что получилось.

Сам тест
Реализация фейкового сервера

Зависимости

Полностью проект можно скачать тут

Интересно
Если будет использоваться асинхронная отсылка http запроса из теста, то синхронизировать сам тест с обработкой запроса можно с помощью метода FakeServer.waitForResponse.
Вот интересная статья про семафоры http://www.baptiste-wicht.com/2010/09/java-concurrency-part-5-monitors-locks-and-conditions/

Saturday, July 28, 2012

PHP для бабушек. Ставим Symfony.


Что такое Symfony? 
Клевый ответ на этот вопрос на сайте разработчиков: "Symfony - это PHP Web Development framework. Что тут неясно? А если неясно, то проще считай, что это религия!" (framework + philosophy+comunity)
Не веришь? Можешь сам перевести отсюда http://symfony.com/what-is-symfony :)

Попробую пройти "обряд крещения". Как обычно расписано по шагам для старичков с хреновой памятью типа меня :)
Вот инструкция по установке и использованию. Я просто следовал ей.

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

1. Установка

Модный способ - через композер. Как поставить композер, описано здесь.
В рутовой папке проекта (у меня это w:\home\calculator\www) запустить такое  (я запускал из гитового баша как обычно):

php composer.phar create-project symfony/framework-standard-edition Symfony/

Нифига не заработало на домашнем компе, но заработало на рабочем. Поэтому следующий вариант такой:

Скачиваю отсюда http://symfony.com/download Symfony Standart и распаковываю в рутовую папку. В руте у меня появится папка Symfony
w\
|-calculator
  |-www
    |-Symfony



Запускаю в папочке w:\calculator\www\Symfony (с баш строки как обычно)
php bin/vendors install

далее идет непериводимая игра слов выдаваемых скриптом и гитом.
Появилась папочка vendor. Сюда Symfony понапихивало массу полезностей.
Все эти полезности описаны в файлике Symfony\deps. На 11м слайде презенташки можно увидеть как он выглядит.

Проверяю. Открываю в браузере * http://www.calculator/Symfony/web/config.php. Вижу страничку конфига.
* www.calculator я добавил в файл Windows\System32\drivers\etc\hosts. Подробнее здесь.


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



2. Создаю свое приложение.

Тут я перепрыгнул на слайд 32. Сначала хочу создать приложение, а потом залить его в git.

Создаю бандл
в папке Symfony запускаю php app/console generate:bundle

Ввожу имя нейпспейса (Tdd/CalculatorBundle) и имя бандла (Calculator).

Дальше куча вопросов, на которые просто жимаю <Enter>

Бандл - это директория со всем, что нужно для фичи. Вот такое мне сгенерилось:


3. Смотрим на тесты

Генератор бандлов также создал папку с тестами

В ней находится один тест на контроллер

Запустить этот тест вместе со сьютом можно из корневой папки Symfony. Запускаю команду
phpunit -c app/
 параметр -c указывает конфигурационный файл для phpunit.

Почему надо запускать тесты с опцией -c?
Тест на контроллер создает и запускает симфонийский кернел со всеми зависимостями. В файле app/phpunit.xml.dist есть ссылка на bootstrap, который делает предварительную инициализацию окружения. 


Настройки Netbeans для запуска тестов
Чтобы мои тесты запускались прямо в Netbeans я сделал следующее:
- Указал где лежат тесты (окно Project properties, пункт Sources)

- Выбрал phpunit.xml.dist в качестеве XML Configuration для тестов (окно Project properties, пункт PHPUnit)

Теперь мои тесты запускаются также из ИДЕ:


Monday, July 09, 2012

Устанавливаем PHPUnit

Инструкция по установке PHPUnit взята отсюда: http://www.phpunit.de/manual/3.0/en/installation.html

Напомню, что все эти операции я проводил на Denwer с версией пхп 5.3.3. Корневая папочка  Денвера замаплена на диск Z:

Итак, чтобы поставить PHPUnit нужно

  1. Убедиться, что установлен Pear. Как его устанавливать я описывал здесь.
  2. Добавить channel, откуда будет установлен пакет PHPUnit:
    pear channel-discover pear.phpunit.de
  3. Добавить channel pear.symfony-project.com (нужен для зависимых модулей)
  4. Собственно, установить PHPUnit командой
    pear install phpunit/PHPUnit
    На ошибку загрузки опциональных зависимостей забиваем.
Дальше буду определяться с IDE и в ней уже создам пару тестов...

Устанавливаем PEAR

Дословный перевод определения PEAR, взятого отсюда http://pear.php.net определения что такое PEAR:
PEAR - это фреймворк и система распределения реюзабельных PHP компонент (PEAR is a framework and distribution system for reusable PHP components)
Pear - это как Setuptools или PIP в питоне.

Ставлю Pear


    1. Поскольку у меня Денвер, то сначала я скачал пакет расширений отсюда  http://www.denwer.ru/packages/php5.html и установил его. 
    2. Этот файл http://pear.php.net/go-pear.phar скачиваем в папку Z:\usr\local\php5\PEAR. Z - это диск, на который я замапил установленную дистрибуцию денвера (см. предидущий пост по установке)

    3. В папке с инсталляцией php запускаем go-pear.bat, на все вопросы жмем Enter.Если нету go-pear.bat, то здесь описан вариант установки руками:  http://szelenin.blogspot.com/2012/07/pear_20.html
    4. На дурацкий вопрос отвечаем Y и подтверждаем еще раз <Enter>  5. Все, pear установлен. Следуя рекомендациям внесем изменения в реестр из сгенерированного файла: 6. У меня внести не получилось, поэтому все переменные из z:\usr\local\php5\PEAR_ENV.reg я внес руками.

    Проверь правильность установки, это ВАЖНО:

      7. PHP_PEAR_PHP_BIN должна содержать полный путь к исполняемому php файлу, то есть к Z:\usr\local\php5\php.exe
       
      8. Директорию Z:\usr\local\php5 нужно добавить в переменную окружения PATH.

Проверка правильности установки Pear


Переходим на диск Z: и запускаем команду pear version. Если видим нечто подобное, значит pear возможно все-таки установлен. 
Чтобы полностью убедиться в правильности устнановки можно выполнить инструкции, описанные здесь
http://pear.php.net/manual/en/installation.checking.php



Friday, March 23, 2012

Расширяем JUnit. Декларативные предусловия.

Нам довольно часто приходится писать тесты, в которых по мере разработки сценариев мы выносим общую логику в setUp. Это хорошая практика, но с увеличением количества тестовых сценариев setUp может разрастись до довольно больших размеров. Мне приходилось сталкиваться с такими тестами. Причем принцип Парето в них зачастую соблюдается так: 80% всех тестов используют 20% функциональности, находящейся в setUp. Кроме сложного setUp каждый тест еще обычно создает свои специфические предусловия. Это довольно сильно усложняет понимание такого теста, т.к. приходится постоянно помнить, что находится в setUp и, к тому же, понимать какие предусловия дополнительно создаются для каждого теста.

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

Итак, предположим нам нужно разработать фильтр для файлов, аналогичный тому, который мы используем в файловых менеджерах. Например если наложить фильтр *.txt, то должен вернутся список текстовых файлов.

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

А было бы легче работать с тестом, который написан таким образом?


Как такое может заработать?


Самый простой вариант - это реализовать свой ранер, наследник от базового класса org.junit.runner.Runner и указывать его в качества "запускальщика":



Полный текст кода класса FileFilterRunner


Что здесь интересного
1. Наследуемся от BlockJUnit4ClassRunner
2. Перекрыт метод runChild. В нем перед вызовом тестового метода создаем временную директорию и заполняем ее тестовыми файлами, имена которых берем из нашей аннотации Given. Для работы с файлами использую библиотеку Apache Commons IO
3. Перекрыт метод validatePublicVoidNoArgMethods. Это позволяет запускать тестовые методы с параметрами
4. Перекрыт метод methodInvoker. Этот метод вызывается JUnit фреймворком для каждого тестового метода
5. Класс ParameterizedInvoker - это наша реализация "вызывальщика" тестовых методов. Мы вызываем тестовый метод с параметром testFolder

Вот сама аннотация Given:


Что получили?


1. Тест стал более читабелным за счет того, что мы вынесли инициализацию предусловий в документирующую аннотацию Given.
2. Реализованный ранер можно легко использовать в других тестах.
3. Если развивать этот подход, то уменьшится необходимость создавать суперкласс для тестовых классов, в котором хранить общую логику для всех наследуемых тестов. Все в аннотациях.
4. Нету setUp/tearDown в тесте
5. Инициализируем только то, что нужно для теста. Если нам, например, понадобится набор папок, то мы добавим в аннотацию параметер folders. Если пользователь с именем "Вася", то - параметр users.
6. Можем ужесточать или ослаблять предусловия. Что если, например, параметр files станет обязательным? Мы уберем инициализацию по умолчанию default {}, тем самым тесты, которые не использовали обязательные параметры перестанут компилироваться и мы их сразу обнаружим
7. Если видишь, чем хорош или плох этот подход - пиши комент, дополню список

Идеи развития


Аннотированные параметры тестового метода. Если есть необходимость передавать разные параметры в тестовый метод, то мы можем их именовать с помощью анотаций, имеющих тип @Target(ElementType.PARAMETER):


Например, нам в тесте понадобился список созданных файлов и путь к корневой директории:


Полный текст исходников и проекта находится здесь качать

Wednesday, February 22, 2012

Coding Dojo - экстремальное погружение

   Не так давно мне посчастливилось попарнокодить с Johannes Brodwall. Один из результатов спаринга я уже опубликовывал в предидущем блоге (см. Зачетный пример на TDD)
   К сожалению мне не удалось попасть на его мастер-класс "TDD Coding Dojo", так как именно в этот день я обещал своей жене то, чего она ждала последние 7 лет - пойти с ней на танцевальную вечеринку. Можете себе представить мою досаду, когда мы, одетые, побритые и накрахмаленные пришли вечером в танцевальный клуб и узнали, что вечеринку перенесли на другой день?!. Еще раз сделаю ссылку на сайт танцевельной тусовки дабы чаще смотреть обновления об изменении планов.
   В общем я задался целью организовать это мероприятие у нас на фирме. Слава Богу, контора большая и желающих должно набраться большое количество. Тем более, что Саша развил в киевском офисе клевую идею с техтолками, что уже создало своеобразную тусовку.
   Итак, я вооружился инструкциями "Как стартануть Coding Dojo" от Johannes'a и решил попробовать запустить это у себя на компе

Как это работает


   Coding Doje Extreme Setup состоит из 2-х частей - клиентская (я ее буду называть Extreme Setup хост, или просто хост) и серверная. Хост - это программа, которая 1 раз в 10 секунд посылает http запросы всем зарегестрированным серверам участников и сравнивает их ответы с правильным вариантом. Если ответ правильный - игрок получает очки, если ответ неправильный - очки отнимаются. Включив принцип КО можно догадаться, что сервер - это программа, запущенная на компьютере участника. В случае с Java - это обычный сервлет

Как установить хост


   Для одной игры хост нужен только один, так что если вы собираетесь только играть можете смело перепрыгивать в секцию Установка сервера.
   Клонируем git://github.com/jhannes/extreme_startup.git. Это форкнутая отсюда https://github.com/rchatley/extreme_startup версия. Выбрал ее потому, что она отработана на киевском dojo на XPDays. Я склонировал в папку C:\workspace\projects\dojo-startup\extreme_startup
   Дальше делаем все согласно README.md из проекта, а именно:
Устанавливаем Ruby 1.9.3 (качаем отсюда)


   Скачиваем Ruby DevKit и распаковываем в (я установил в c:\workspace\bin\RubyDevKit)


   Далее устанавливаем DevKit:
- Открываем командную строку, переходим в c:\workspace\bin\RubyDevKit и запускаем ruby dk.rb init.

- Сгенерировался config.yml файл. Откроем и посмотрим. Должны увидеть путь к установленному Ruby.

Если нет – надо добавить

- Запускаем ruby dk.rb install



   Переходим в папку со склонированным репозиторием (C:\workspace\projects\dojo-startup\extreme_startup) и запускаем команду gem install bundler.

   Затем запускаем команду bundle install и ждем, пока скачаются и установятся необходимые библиотеки. После появления обнадеживающей надписи

   запустим extreme startup сервер в режиме разогрева (warmup mode): ruby warmup_web_server.rb

   Проверяем страничку с результатами http://localhost:3000 (пока пустая). Проверяем, что хост доступен с другого компьютера.

   Чтобы присоединиться к игре необходимо ввести имя и URL веб сервера игрока, запущенного на его машине. Для каждого языка программирования существует свой простой способ стартануть сервер. Я привел инструкции как это сделать на Java. Для других языков смотрите список проектов в каталоге extreme_startup_servers
   Ждем, когда все игроки настроят свои средства разработки и зарегистрируются. Как только все прокричали «я готов!» останавливаем сервер и запускаем реальную игру командой ruby web_server.rb

Установка сервера на Java.


   Прежде всего нужно убедиться, что установлен Maven (если нет, то утанавливаем отсюда) и он доступен по путям: mvn –version что-то выдает
   Скачиваем зип архив отсюда. Это содержимое репозитория со старт-поинтами https://github.com/bodil/extreme_startup_servers. Я склонировал в папку C:\workspace\projects\dojo-startup\extreme_startup_servers
   Запускаем любимую java IDE (в моем случае это IntelliJ IDEA) и создаем новый проект из мавеновского pom.xml. Проект берем этот: /java/servlet_for_dummies (C:\workspace\projects\dojo-startup\extreme_startup_servers\java\servlet_for_dummies)

   После открытия проекта смотрим что внутри


  • ExtremeStartupServer – класс с main методом для запуска Jetty
  • ExtremeStartup – сервлет, который мы будем программировать
  • ExtremeStartupTest – тест, который нам поможет этот сервлет запрограммировать

       Запустим ExtremeStartupTest – если все тесты зеленые, то запускаем сервер (ExtremeStartupServer)
       Открываем браузер и вводим адрес http://localhost:1337/?q=what+is+the+sum+of+8+and+22


       Все, теперь можем регистрировать наш сервер. Переходим на страничку хоста (http://localhost:3000 или в случае другого компа – IP адрес этой машины), нажимаем I want to play и вводим имя (латинскими буквами!) и URL вашего сервера.

       Если хост смог достучаться до сервера, то увидим примерно такую обнадеживающую надпись


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


       Выведем на консоль то, что спрашивает хост – делаем изменения в классе ExtremeStartup
       Оказывается хост хочет узнать как меня зовут.


       Не вопрос, поменяем реализацию метода answer
       и смотрим на свой результат. Ура! У меня есть первый правильный ответ


       Итак, в консоли мы видим приходящие запросы и программируем сервлет так, чтобы он отдавал правильные результаты. За каждый правильный результат начисляются баллы, за каждый неправильный или пропущенный ответ вычитаются. Думаю очень полезно использовать тест, чтобы не поломать предыдущую функциональность и иметь возможность быстро сэмулировать запрос.
       Все, можно кричать "Я готов!"
  • Thursday, December 22, 2011

    Зачетный пример на TDD - Prime Factors

    Недавно мне посчастливилось попрактиковаться в парном программировании с Johannes Brodwall во время его визита в Киев на конференцию XP Days. Мы, играючи в ТДД пинг понг, за пару часов реализовали 2 довольно сложных примерчика, так называемых kata для его coding dojo сессии на XP Days. Причем успели один из примеров реализовать не только на Java, но еще на CoffeScript.

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

    Если ты еще не знаком с правилами ТДД пинг понг, то рекомендую посмотреть как это делается здесь или попрактиковаться на нашем треннинге по ТДД.

    "Prime Factors" (не знаю как по-русски).
    Разложить целое число на множители из простых чисел:
    2 -> [2]
    3 -> [3]
    4 -> [2,2]
    6 -> [2,3]
    9 -> [3,3]
    12 -> [2,2,3]
    15 -> [3,5]
    и так далее...

    Итак, начали...
    Johannes. Естественно с теста:

    public class PrimeTest {
    @Test
    public void primeFactorOfOne() {
    assertEquals("[]", Prime.factorsOf(1).toString());
    }
    Я. А че тут думать? Самая простая реализация - вот:
    Тесты прошли, пишу следующий тест:
    junit.framework.ComparisonFailure:
    Expected  :[2]
    Actual    :[]

    Johannes. Тоже не особо думая
    и, после запуска тестов - пытается отдать мне это:
    Oops... тест зеленый, сори, мне идет эта подача:
    junit.framework.ComparisonFailure:
    Expected  :[2, 2]
    Actual    :[4]

    Я. Лень что-то придумывать сложное. Первая мысль - добавить тупой if и разделить на 2
    как ни странно, но прошло. Тихо радуюсь про себя, что не мне реализовывать следующий тест:
    junit.framework.ComparisonFailure:
    Expected  :[5]
    Actual    :[2, 2]

    Johannes. Видимо, почувствовал мое злорадство и тоже долгими раздумьями над идеальным решением себя не утрудил:
    и, как говорится, "получите, распишитесь" - забираю клавиатуру с таким красным тестом:
    junit.framework.ComparisonFailure:
    Expected  :[2, 3]
    Actual    :[3, 3]

    Я. Тут я немного подвис. Смотрю на то, как падает тест и не без помощи напарника понимаю, что для того, чтобы в списке появилась 2, а потом 3, нужно организовать цикл:
    Ура! Работает! Все тесты зеленые. Пробую добавить тест на семерку - проходит. Поправляю на 8 и отдаю клавиатуру
    junit.framework.ComparisonFailure:
    Expected  :[2, 2, 2]
    Actual    :[2, 4]

    Johannes. Все, что надо сделать - это заменить if на while:

    Все тесты зеленые! Я пытаюсь добавить тест на двухзнчные числа, потом думаю подловить, предав что-то вроде такого 2*3*5*7*11*13*17*19*23, потом отнимаю от этого выражения 1, затем 2 но поломать код не получается - тесты зеленые! Все, сдаюсь и соглашаюсь: РАБОТАЕТ!

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

    И "пофиксили" его вот таким изменением:

    Все, теперь не только РАБОТАЕТ, но и работает БЫСТРО!