Комментарии 31
Мне понравилось. Легко написано, с любовью что-ли. И со знанием дела. Возьму на заметку.
Спасибо! Я действительно люблю писать тесты к своему коду. Я по жизни — существо достаточно безалаберное, и с начала своей карьеры программиста всегда с завистью смотрел на «крутых спецов», которые открывают на экране сразу пять окон vi, держат все в голове, пишут код, который сразу работает. А когда я открыл для себя тесты, я обнаружил, что этого всего не нужно, чтобы быть хорошим спецом. Достаточно написать пару тестов, и они гарантируют, что твой код не сломается в самый ответственный (или позорный) момент. Можно написать тест прямо по техзаданию и иметь возможность доказать, что это не моя проблема, а проблема проектировщика, писавшего техзадание. Можно не держать все детали всех сотен классов в голове, а в любой момент посмотреть, что каждый из них должен делать в какой момент. Короче, без них я бы не имел никакой возможности сойти за крутого :) Плюс у тестов (особенно, integration тестов) есть такая особенность, что они часто показывают проблемы за пределами того, что они проверяют. На последнем проекте гигансткое количество мелких огрешностей с Hibernate маппингами было отловлено в процессе написания end-to-end тестов — то есть тестирования всего приложения, а не отдельных модулей. Причем такие вещи всплывали, что их и не догадаешься специально тестировать.

Короче, без тестов я бы никогда не смог отвечать за свой код и код своей команды. И спать спокойно в ночь, когда наш продукт устанавливался у клиента (я реально спал, и начальству написал, что, мол, разбудите, если что, но я думаю, что не понадобится. И не понадобилось, что мне очков прибавило).
Да… нервы экономит. Все тесты отработали — уже намного увереннее себя чувствуешь. Особенно если к их написания отнесся ответственно (хотя и в тестах ошибки встречаются и недочеты, но это все равно очень помогает).
Автору спасибо.
С недавних пор использую tdd в проектах на C# (сейчас даже игру пишу под wp7 используя tdd).
Многие боятся tdd. Не бойтесь! смело пробуйте, там много профита.
— мелкие глюки, которые можно допустить, когда пишешь без тестов (где-нибудь переменную цикла не правильно используешь, X и Y при копировании реализаций где-нибудь заменить забудешь и прочее)
— с утра делаешь update (или pull), прогоняешь тесты и садишься дальше писать с душевным спокойствием, что все работает как должно
— рефакторить вообще сказка, чуть поменял реализацию или применил оптимизацию, и по тестам сразу видно что все в порядке или где-то что-то перестало работать так как ожидается (при большом проценте покрытия кода тестами)
— при написании теста сразу можно прикинуть интерфейс, который хочешь увидеть от новой функции, класса и т.д. (тест же пишем первым делом). Далее просто реализовываешь это интерфейс.
— все делается мелкими шагами, без погружения в глубокую реализацию когда не нужно.

во хотя бы это, с оглядкой на мой опыт.
>— с утра делаешь update (или pull), прогоняешь тесты и садишься дальше писать с душевным спокойствием, что все работает как должно

А вот тут позвольте с вами не согласиться :) Я стараюсь оставить какой-нибудь тест сломанным на ночь. Тогда с утра легче раскачаться, особенно, если примерно знаешь, что поломано и как починить.
Интересное предложение.
Но я, веду список мелких фич в ToDo листе в Visual Studio, которые надо будет реализовать дальше по ходу работы.
По мере появления мыслей в голове записываю туда, как только тест зеленый, смотрю в список и выбираю что дальше буду делать.
Так что с утра сразу вижу, с чего можно начать :)
Спасибо за вменяемый развёрнутый ответ.

Я долго думал — зачем и когда нужно применять тесты. Ваш ответ меня в очередной раз убедил, что тесты нужны не как двигатель разработки и не как самоцель (идеология фундаменталистского радикального TDD), а лишь как дополнительное средство обеспечения качества разработки и душевного спокойствия.

Другими словами — тестами надо покрывать критические места и стыки зон ответственностей. И не надо стремиться к 100%-му покрытию кода, потому что соотношение «бессмысленная работа / польза» здесь начинает становиться страшным.
100% это утопия конечно.
Простые get { return _count; } можно и не тестировать.
Я пишу тесты на начальные данные, функционал, и exceptions.
А как покрываются тестами атрибуты, событийная логика? Или используются, грубо говоря, «арифметические» тесты, на уровне правильности некоторых наборов данных?

ЗЫ. функционал — это не очень правильное слово для алгоритмической начинки. Без обид :)
функционал это функционал. Туда входит все что ожидаешь от объекта.
Функциона́л — числовая функция, заданная на векторном пространстве ;)
Или еще пример — как без нормальных тестов можно сливать свой код с чьим-то (если в команде 2 и больше программистов)? У меня что, голова безразмерная — думать обо всех глупостях, которые придут в голову очередному гению? А так я просто делаю git pull; mvn clean install — и все. А уж потом, в конце дня — code review, чтобы ручки шаловливые пообломать, если понадобится.
Спасибо за статью. Есть к вам пара вопросов.

Скажите, пожалуйста, насчет тестов базы и использования Hypersonic — получается, вы создаете по инстансу Hypersonic под каждый тест? Вообще, я бы с радостью прочитал статью с подробностями.

И еще — я правильно понимаю, что проект ORMUnit умер в 2007 году? Он при этом достаточно работоспособен?
Не совсем под каждый тест. Spring context создается под каждый тестовый класс. Перед каждым тестом (в setUp() абстрактного родительского теста) через ORMUnit находится LocalSessionFactoryBean (из Hibernate), и на нем делается dropDatabaseSchema(); createDatabaseSchema();.

Тут есть возможности для улучшения. В большом числе случаев достаточно создать Spring context один раз для всех тестов, которые наследуют от какого-то корня. Но во времена оны (когда писался ORMUnit), JUnit4 и TestNG не существовало еще, и поэтому нельзя было без танцев с бубном сделать @BeforeSuite. Теперь можно. Надо бы взять ORMUnit и переписать для использования с JUnit4.

А так — да, ORMUnit мы используем до сих пор. Единственная проблема — он заставляет использовать JUnit 3. Но это не очень большая проблема.
Если совсем честно, из ORMUnit мы реально используем только код, который пересоздает базу. Посмотрите исходники, написать этот кусок самому — нечего делать: Вот тут все написано
Пардон, наврал. Мы еще активно используем doWithTransaction(TransactionCallback). В лучших традициях Spring-а у нас транзакции объявлены только вокруг методов из сервисов. Поэтому иногда в тестах нужно вручную обернуть какие-то вызовы в транзакции (например, вызовы методов из Repository). Тоже не сложно написать самому и использовать с любыми базовыми классами для тестов. хоть Spring-овыми, хоть какими.
У меня тоже вопрос появился. Во время тестов вы используете реальную СУБД или что-то, которое крутиться, так скажем, in-memory?
Мы просто используем следующую методику на девелоперской машине используем легкую реализацию в памяти(например hsqldb), которая быстро поднимается в памяти, таким образом время на интеграционный тест уменьшается, так как создается один instance на каждую TestSuite.
На тестовом сервер при прогонке тестов уже соединяемся с базой подобной по настройкам с продакшеном, для того чтобы посмотреть на наличие ошибок в боевых условиях(бывали случаи что код работает прекрасно на in-memory и валятся на тестовом).
Мы для тестов тоже используем аналогичный подход, но вместо hsqldb — in-memory h2.
Причём, если использовать spring, то всё делается буквально 1 строкой:
да что ж такое, комментарий раньше времени отправился (продолжаю):
<jdbc:embedded-database id="dataSource" type="H2" />
Там же можно указать и скрипт инициализации:
<jdbc:embedded-database id="dataSource" type="H2" >
<jdbc:script location="" />
</jdbc:embedded-database>

Ну или просто указать в настройках хибернейта hibernate.hbm2ddl.auto=create.
Ага, правда я бы сказал не следующая, а «от того же автора», во всяком случае вики говорит, что код написан заново.
Ну да, код полностью переписан, но нам-то какое до этого дело? :) Главное, что он по-прежнему запускается в памяти из Джавы и разговаривает на SQL-е.
Hypersonic — это и есть hsqldb (Hypersonic SQL DB)… И именно in-memory.

Есть у нас группа тестов (небольшая), которая соединяется с тестовым сервером и делает несколько проверок, только чтобы убедиться, что новый код попал на тестовый сервер, миграция базы данных прошла успешно, и т.д.
Почему-то думал, что H означает Hibernate О_о

Кстати, как вы отслеживаете изменение структуры БД между версиями? Используете какие-то тулзы, вроде Liquidbase или просто diff между дампами?
Мы пользуемся dbdeploy. Работает, особенно, когда не очень много народу в команде. Когда много — начинаются проблемы с дисциплиной.
Я тоже люблю писать тесты :) Поделюсь своими наблюдениями: 1) Если код может быть адекватно протестирован интеграционно, то в большинстве случаев (не всегда, конечно) такого теста достаточно, и не нужно выдумывать «как протестировать код в изоляции». Это сработает, если в коде не слишком много слое абстракции, например в несложных веб-приложениях обычно один-два уровня сервисов и все. 2) Что бы тест «удался», сложные компоненты лучше всего замещать fake-объектами, hsql/h2 в памяти вместо реальной БД хороший тому пример. Но тут, можно сказать, повезло: есть готовый фейк. Но даже если придется самому реализовывать фейк, это того стоит. Моки (и поведенческое тестирование вместо результативного как следствие) следует использовать только в крайнем случае, либо там, где нужно именно поведенческое тестирование. Тесты, изобилирующие моками, весьма ломки и трудны в поддержке. Да и сложны в восприятии, так как проверяют они не конечный результат а подробности реализации вроде «а вызвал б N раз». К примеру в вашем коде я бы замочил только VirtualInfrastructureService, а DeploymentMaker взял бы реальный, DeploymentRepositiry — написал бы фейк на HashMap-е. Резюмирая: изоляция ради изоляции — зло. В свое время меня вдохновила статья Фаулера: martinfowler.com/articles/mocksArentStubs.html
К сожалению, мой пример с DeploymentServiceImpl.deploy() немного неудачный. В реальности, там есть пара-тройка веток исполнения (хотя весь метод не превышает 15 строк), и мне нужно быть уверенным, что всегда, после всех рефакторингов, если исходные данные нормальные, новый деплоймент будет записан в базу, послан мессидж в MQ, и еще произойдет пара вещей. Именно поэтому мне было легче сделать моки, которые бы сразу возвращали нужные мне результаты проверки исходных данных. Хотя, конечно, этого же можно было бы достичь через использование Mothers (т.е ApplicationMother.makeValidApp(), DeploymentDescriptionMother.makeValidDescriptionForApp(app) )
И да, согласен, что далеко не на каждый метод нужен поведенческий тест в изоляции. Только на те, которые принимают решения и на основе этих решений делают что-то с, грубо говоря, вводом-выводом.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.