Меню

Tdd что это: TDDx2, BDD, DDD, FDD, MDD и PDD, или все, что вы хотите узнать о Driven Development / Habr – Разработка через тестирование — Википедия

Содержание

Разработка через тестирование — Википедия

Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам. Кент Бек, считающийся изобретателем этой техники, утверждал в 2003 году, что разработка через тестирование поощряет простой дизайн и внушает уверенность (англ. inspires confidence)[1].

В 1999 году при своём появлении разработка через тестирование была тесно связана с концепцией «сначала тест» (англ. test-first), применяемой в экстремальном программировании[2], однако позже выделилась как независимая методология.[3].

Тест — это процедура, которая позволяет либо подтвердить, либо опровергнуть работоспособность кода. Когда программист проверяет работоспособность разработанного им кода, он выполняет тестирование вручную.

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

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

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

Разумеется, к тестам применяются те же требования стандартов кодирования, что и к основному коду.

Графическое представление цикла разработки, в виде блок-схемы

Приведенная последовательность действий основана на книге Кента Бека «Разработка через тестирование: на примере» (англ. Test Driven Development: By Example).[1]

Добавление теста[править | править код]

При разработке через тестирование, добавление каждой новой функциональности (англ. feature) в программу начинается с написания теста. Неизбежно этот тест не будет проходить, поскольку соответствующий код ещё не написан. (Если же написанный тест прошёл, это означает, что либо предложенная «новая» функциональность уже существует, либо тест имеет недостатки.) Чтобы написать тест, разработчик должен чётко понимать предъявляемые к новой возможности требования. Для этого рассматриваются возможные сценарии использования и пользовательские истории. Новые требования могут также повлечь изменение существующих тестов. Это отличает разработку через тестирование от техник, когда тесты пишутся после того, как код уже написан: она заставляет разработчика сфокусироваться на требованиях до написания кода — тонкое, но важное отличие.

Запуск всех тестов: убедиться, что новые тесты не проходят[править | править код]

На этом этапе проверяют, что только что написанные тесты не проходят. Этот этап также проверяет сами тесты: написанный тест может проходить всегда и соответственно быть бесполезным. Новые тесты должны не проходить по объяснимым причинам. Это увеличит уверенность (хотя не будет гарантировать полностью), что тест действительно тестирует то, для чего он был разработан.

Написать код[править | править код]

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

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

Запуск всех тестов: убедиться, что все тесты проходят[править | править код]

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

Рефакторинг[править | править код]

Когда достигнута требуемая функциональность, на этом этапе код может быть почищен. Рефакторинг — процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы, устранить дублирование кода, облегчить внесение изменений в ближайшем будущем.

Повторить цикл[править | править код]

Описанный цикл повторяется, реализуя всё новую и новую функциональность. Шаги следует делать небольшими, от 1 до 10 изменений между запусками тестов. Если новый код не удовлетворяет новым тестам или старые тесты перестают проходить, программист должен вернуться к отладке. При использовании сторонних библиотек не следует делать настолько небольшие изменения, которые буквально тестируют саму стороннюю библиотеку[3], а не код, её использующий, если только нет подозрений, что библиотека содержит ошибки.

Разработка через тестирование тесно связана с такими принципами как «делай проще, дурачок» (англ. keep it simple, stupid, KISS) и «вам это не понадобится» (англ. you ain’t gonna need it, YAGNI). Дизайн может быть чище и яснее, при написании лишь того кода, который необходим для прохождения теста.[1] Кент Бек также предлагает принцип «подделай, пока не сделаешь» (англ. fake it till you make it). Тесты должны писаться для тестируемой функциональности. Считается, что это имеет два преимущества. Это помогает убедиться, что приложение пригодно для тестирования, поскольку разработчику придется с самого начала обдумать то, как приложение будет тестироваться. Это также способствует тому, что тестами будет покрыта вся функциональность. Когда функциональность пишется до тестов, разработчики и организации склонны переходить к реализации следующей функциональности, не протестировав существующую.

Идея проверять, что вновь написанный тест не проходит, помогает убедиться, что тест реально что-то проверяет. Только после этой проверки следует приступать к реализации новой функциональности. Этот приём, известный как «красный/зелёный/рефакторинг», называют «мантрой разработки через тестирование». Под красным здесь понимают не прошедшие тесты, а под зелёным — прошедшие.

Отработанные практики разработки через тестирование привели к созданию техники «разработка через приёмочное тестирование» (англ. Acceptance Test-driven development, ATDD), в котором критерии, описанные заказчиком, автоматизируются в приёмочные тесты, используемые потом в обычном процессе разработки через модульное тестирование (англ. unit test-driven development, UTDD).

[4] Этот процесс позволяет гарантировать, что приложение удовлетворяет сформулированным требованиям. При разработке через приёмочное тестирование, команда разработчиков сконцентрирована на чёткой задаче: удовлетворить приёмочные тесты, которые отражают соответствующие требования пользователя.

Приёмочные (функциональные) тесты (англ. customer tests, acceptance tests) — тесты, проверяющие функциональность приложения на соответствие требованиям заказчика. Приёмочные тесты проходят на стороне заказчика. Это помогает ему быть уверенным в том, что он получит всю необходимую функциональность.

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

[5] Гипотезы, связывающие качество кода с TDD, были неубедительны.[6]

Программисты, использующие TDD на новых проектах, отмечают, что они реже ощущают необходимость использовать отладчик. Если некоторые из тестов неожиданно перестают проходить, откат к последней версии, которая проходит все тесты, может быть более продуктивным, нежели отладка.[7]

Разработка через тестирование предлагает больше, чем просто проверку корректности, она также влияет на дизайн программы. Изначально сфокусировавшись на тестах, проще представить, какая функциональность необходима пользователю. Таким образом, разработчик продумывает детали интерфейса до реализации. Тесты заставляют делать свой код более приспособленным для тестирования. Например, отказываться от глобальных переменных, одиночек (singletons), делать классы менее связанными и легкими для использования. Сильно связанный код или код, который требует сложной инициализации, будет значительно труднее протестировать. Модульное тестирование способствует формированию четких и небольших интерфейсов. Каждый класс будет выполнять определенную роль, как правило, небольшую. Как следствие, зависимости между классами будут снижаться, а зацепление повышаться. Контрактное программирование (англ. design by contract) дополняет тестирование, формируя необходимые требования через утверждения (англ. assertions).

Несмотря на то, что при разработке через тестирование требуется написать большее количество кода, общее время, затраченное на разработку, обычно оказывается меньше. Тесты защищают от ошибок. Поэтому время, затрачиваемое на отладку, снижается многократно.[8] Большое количество тестов помогает уменьшить количество ошибок в коде. Устранение дефектов на более раннем этапе разработки, препятствует появлению хронических и дорогостоящих ошибок, приводящих к длительной и утомительной отладке в дальнейшем.

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

Разработка через тестирование способствует более модульному, гибкому и расширяемому коду. Это связано с тем, что при этой методологии разработчику необходимо думать о программе как о множестве небольших модулей, которые написаны и протестированы независимо и лишь потом соединены вместе. Это приводит к меньшим, более специализированным классам, уменьшению связанности и более чистым интерфейсам. Использование mock-объектов также вносит вклад в модуляризацию кода, поскольку требует наличия простого механизма для переключения между mock- и обычными классами.

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

Тесты могут использоваться в качестве документации. Хороший код расскажет о том, как он работает, лучше любой документации. Документация и комментарии в коде могут устаревать. Это может сбивать с толку разработчиков, изучающих код. А так как документация, в отличие от тестов, не может сказать, что она устарела, такие ситуации, когда документация не соответствует действительности — не редкость.

  • Существуют задачи, которые невозможно (по крайней мере, на текущий момент) решить только при помощи тестов. В частности, TDD не позволяет механически продемонстрировать адекватность разработанного кода в области безопасности данных и взаимодействия между процессами. Безусловно, безопасность основана на коде, в котором не должно быть дефектов, однако она основана также на участии человека в процедурах защиты данных. Тонкие проблемы, возникающие в области взаимодействия между процессами, невозможно с уверенностью воспроизвести, просто запустив некоторый код.
  • Разработку через тестирование сложно применять в тех случаях, когда для тестирования необходимо прохождение функциональных тестов. Примерами может быть: разработка интерфейсов пользователя, программ, работающих с базами данных, а также того, что зависит от специфической конфигурации сети. Разработка через тестирование не предполагает большого объёма работы по тестированию такого рода вещей. Она сосредотачивается на тестировании отдельно взятых модулей, используя mock-объекты для представления внешнего мира.
  • Требуется больше времени на разработку и поддержку, а одобрение со стороны руководства очень важно. Если у организации нет уверенности в том, что разработка через тестирование улучшит качество продукта, то время, потраченное на написание тестов, может рассматриваться как потраченное впустую.[9]
  • Модульные тесты, создаваемые при разработке через тестирование, обычно пишутся теми же, кто пишет тестируемый код. Если разработчик неправильно истолковал требования к приложению, и тест, и тестируемый модуль будут содержать ошибку.
  • Большое количество используемых тестов может создать ложное ощущение надежности, приводящее к меньшему количеству действий по контролю качества.
  • Тесты сами по себе являются источником накладных расходов. Плохо написанные тесты, например, содержат жёстко вшитые строки с сообщениями об ошибках или подвержены ошибкам, дороги при поддержке. Чтобы упростить поддержку тестов, следует повторно использовать сообщения об ошибках из тестируемого кода.
  • Уровень покрытия тестами, получаемый в результате разработки через тестирование, не может быть легко получен впоследствии. Исходные тесты становятся всё более ценными с течением времени. Если неудачные архитектура, дизайн или стратегия тестирования приводят к большому количеству непройденных тестов, важно их все исправить в индивидуальном порядке. Простое удаление, отключение или поспешное изменение их может привести к необнаруживаемым пробелам в покрытии тестами.

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

Из кода теста может не быть доступа к приватным (англ. private) полям и методам. Поэтому при модульном тестировании может потребоваться дополнительная работа. В Java разработчик может использовать отражение (англ. reflection), чтобы обращаться к полям, помеченными как приватные.[10] Модульные тесты можно реализовать во внутренних классах, чтобы они имели доступ к членам внешнего класса. В .NET Framework могут применяться разделяемые классы (англ. partial classes) для доступа из теста к приватным полям и методам.

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

Не существует единого мнения среди программистов, применяющих разработку через тестирование, о том, насколько осмысленно тестировать приватные, защищённые(англ. protected) методы, а также данные. Одни убеждены, что достаточно протестировать любой класс только через его публичный интерфейс, поскольку приватные переменные — это всего лишь деталь реализации, которая может меняться, и её изменения не должны отражаться на наборе тестов. Другие утверждают, что важные аспекты функциональности могут быть реализованы в приватных методах и тестирование их неявно через публичный интерфейс лишь усложнит ситуацию: модульное тестирование предполагает тестирование наименьших возможных модулей функциональности.[11][12]

Fake-, mock-объекты и интеграционные тесты[править | править код]

Модульные тесты тестируют каждый модуль по отдельности. Неважно, содержит ли модуль сотни тестов или только пять. Тесты, используемые при разработке через тестирование, не должны пересекать границы процесса, использовать сетевые соединения. В противном случае прохождение тестов будет занимать большое время, и разработчики будут реже запускать набор тестов целиком. Введение зависимости от внешних модулей или данных также превращает модульные тесты в интеграционные. При этом если один модуль в цепочке ведет себя неправильно, может быть не сразу понятно какой именно[источник не указан 2731 день].

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

  1. Везде, где требуется доступ к внешним ресурсам, должен быть объявлен интерфейс, через который этот доступ будет осуществляться. См. принцип инверсии зависимостей (англ. dependency inversion) для обсуждения преимуществ этого подхода независимо от TDD.
  2. Интерфейс должен иметь две реализации. Первая, собственно предоставляющая доступ к ресурсу, и вторая, являющаяся fake- или mock-объектом. Всё, что делают fake-объекты, это добавляют сообщения вида «Объект person сохранен» в лог, чтобы потом проверить правильность поведения. Mock-объекты отличаются от fake- тем, что сами содержат утверждения (англ. assertion), проверяющие поведение тестируемого кода. Методы fake- и mock-объектов, возвращающие данные, можно настроить так, чтобы они возвращали при тестировании одни и те же правдоподобные данные. Они могут эмулировать ошибки так, чтобы код обработки ошибок мог быть тщательно протестирован. Другими примерами fake-служб, полезными при разработке через тестирование, могут быть: служба кодирования, которая не кодирует данные, генератор случайных чисел, который всегда выдает единицу. Fake- или mock-реализации являются примерами внедрения зависимости (англ. dependency injection).

Использование fake- и mock-объектов для представления внешнего мира приводит к тому, что настоящая база данных и другой внешний код не будут протестированы в результате процесса разработки через тестирование. Чтобы избежать ошибок, необходимы тесты реальных реализаций интерфейсов, описанных выше. Эти тесты могут быть отделены от остальных модульных тестов и реально являются интеграционными тестами. Их необходимо меньше, чем модульных, и они могут запускаться реже. Тем не менее, чаще всего они реализуются используя те же библиотеки для тестирования (англ. testing framework), что и модульные тесты.

Интеграционные тесты, которые изменяют данные в базе данных, должны откатывать состоянии базы данных к тому, которое было до запуска теста, даже если тест не прошёл. Для этого часто применяются следующие техники:

  • Метод TearDown, присутствующий в большинстве библиотек для тестирования.
  • try...catch...finally структуры обработки исключений, там где они доступны.
  • Транзакции баз данных.
  • Создание снимка (англ. snapshot) базы данных перед запуском тестов и откат к нему после окончания тестирования.
  • Сброс базы данных в чистое состояние перед тестом, а не после них. Это может быть удобно, если интересно посмотреть состояние базы данных, оставшееся после не прошедшего теста.

Существуют библиотеки Moq, jMock, NMock, EasyMock, Typemock, jMockit, Unitils, Mockito, Mockachino, PowerMock или Rhino Mocks, а также sinon для JavaScript предназначенные упростить процесс создания mock-объектов.

  1. 1 2 3 Beck, K. Test-Driven Development by Example, Addison Wesley, 2003
  2. Lee Copeland. Extreme Programming (неопр.). Computerworld (December 2001). Дата обращения 11 января 2011. Архивировано 27 августа 2011 года.
  3. 1 2 Newkirk, JW and Vorontsov, AA. Test-Driven Development in Microsoft .NET, Microsoft Press, 2004
  4. ↑ Koskela, L. «Test Driven: TDD and Acceptance TDD for Java Developers», Manning Publications, 2007
  5. Erdogmus, Hakan; Morisio, Torchiano. On the Effectiveness of Test-first Approach to Programming (неопр.) (недоступная ссылка). Proceedings of the IEEE Transactions on Software Engineering, 31(1). January 2005. (NRC 47445). — «We found that test-first students on average wrote more tests and, in turn, students who wrote more tests tended to be more productive.». Дата обращения 14 января 2008. Архивировано 27 августа 2011 года.
  6. Proffitt, Jacob TDD Proven Effective! Or is it? (неопр.) (недоступная ссылка). — «So TDD’s relationship to quality is problematic at best. Its relationship to productivity is more interesting. I hope there’s a follow-up study because the productivity numbers simply don’t add up very well to me. There is an undeniable correlation between productivity and the number of tests, but that correlation is actually stronger in the non-TDD group (which had a single outlier compared to roughly half of the TDD group being outside the 95% band).». Дата обращения 21 февраля 2008. Архивировано 27 августа 2011 года.
  7. Llopis, Noel Stepping Through the Looking Glass: Test-Driven Game Development (Part 1) (неопр.) (недоступная ссылка). Games from Within (20 February 2005). — «Comparing [TDD] to the non-test-driven development approach, you’re replacing all the mental checking and debugger stepping with code that verifies that your program does exactly what you intended it to do.». Дата обращения 1 ноября 2007. Архивировано 22 февраля 2005 года.
  8. Müller, Matthias M.; Padberg, Frank. About the Return on Investment of Test-Driven Development (неопр.) (PDF) 6. Universität Karlsruhe, Germany. Дата обращения 1 ноября 2007. Архивировано 27 августа 2011 года.
  9. Loughran, Steve Testing (неопр.) (PDF). HP Laboratories (November 6th, 2006). Дата обращения 12 августа 2009. Архивировано 27 августа 2011 года.
  10. Burton, Ross Subverting Java Access Protection for Unit Testing (неопр.). O’Reilly Media, Inc. (11/12/2003). Дата обращения 12 августа 2009. Архивировано 27 августа 2011 года.
  11. Newkirk, James Testing Private Methods/Member Variables — Should you or shouldn’t you (неопр.). Microsoft Corporation (7 June 2004). Дата обращения 12 августа 2009. Архивировано 27 августа 2011 года.
  12. Stall, Tim How to Test Private and Protected methods in .NET (неопр.). CodeProject (1 Mar 2005). Дата обращения 12 августа 2009. Архивировано 27 августа 2011 года.
  • Кент Бек. Экстремальное программирование: разработка через тестирование = Test-driven Development. — Питер, 2003. — 224 с. — ISBN 5-8046-0051-6, 0-321-14653-0.
  • Лайза Криспин, Джанет Грегори. Гибкое тестирование: практическое руководство для тестировщиков ПО и гибких команд = Agile Testing: A Practical Guide for Testers and Agile Teams. — М.: «Вильямс», 2010. — 464 с. — (Addison-Wesley Signature Series). — 1000 экз. — ISBN 978-5-8459-1625-9.

BDD — рабочий метод или TDD в модной обертке? / JUG Ru Group corporate blog / Habr

Два подхода к разработке через тестирование вызывают особенно много споров — из-за некоторого методологического сходства TDD (Test Driven Development) и BDD (Behaviour Driven Development) часто путают даже профессионалы. Старшие инженеры по автоматизации тестирования «Альфа-Лаборатории» Юлия Ковалева и Анна Чернышева рассказывают базовые вещи о сходстве и различиях двух популярных методик и то, какой подход у них используется в самой компании.


Юлия Ковалева, старший Java-разработчик автотестов в «Альфа-Лаборатории»
Посвятила тестированию более 4 лет, сейчас разрабатывает библиотеку шагов для масштабирования автоматизации тестирования с использованием Cucumber и Selenide.

Анна Чернышева, старший Java-разработчик автотестов в «Альфа-Лаборатории»
Работала в крупных e-commerce проектах, участвует в создании и поддержке нескольких BDD-фреймворков, а также занимается внедрением инженерных и DevOps-практик.

— В чем заключается основное различие методик TDD и BDD?

Анна Чернышева: Понимание методик TDD и BDD отличается в разных компаниях, мы расскажем о том, как все устроено в «Альфа-Лаборатории». Концепции обоих подходов похожи, сначала идут тесты и только потом начинается разработка, но предназначение у них совершенно разное. TDD — это больше о программировании и тестировании на уровне технической реализации продукта, когда тесты создают сами разработчики. BDD предполагает описание тестировщиком или аналитиком пользовательских сценариев на естественном языке — если можно так выразиться, на языке бизнеса.

— BDD — просто модное слово или принципиально новый подход к разработке через тестирование? От TDD его отличает только использование естественных языков для описания тестов?

Юлия Ковалева: BDD и в самом деле модное слово, но далеко не все умеют его правильно «готовить». В «Альфа-Лаборатории» нам пришлось комплексно подойти к решению задач и полностью изменить многие аспекты функционирования всей команды, что позволило существенно удешевить процесс тестирования. Нанять умеющего описывать тестовые сценарии на русском языке человека намного проще, чем найти специалиста, способного реализовать эти тесты, например, на Java.

BDD подход совместно с инженерными практиками позволил нам отказаться от legacy-документации, содержащей неактуальную информацию, и получать новую документацию налету, хранить ее вместе с проектом, что позволило приблизить аналитиков и тестировщиков к коду.

— В разработку сценариев BDD вовлечены не только тестировщики, необходимость в автоматизаторах сохраняется?

Юлия Ковалева: Разумеется, оставить в команде только тестировщиков без автоматизаторов — достаточно недальновидно. Потребуется некий инструментарий и владеющий им человек, чтобы реализовать ту техническую составляющую, которую тестировщик самостоятельно сделать не сможет.

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

— Помимо традиционных для TDD unit-тестов при использовании BDD-подхода проводятся также behavior-тесты. На этом различия процессов тестирования заканчиваются или дело обстоит сложнее?

Анна Чернышева: Все гораздо сложнее, поскольку BDD — скорее процесс, целью которого является удешевление реализации новых фич. Еще на старте разработки мы получаем важные артефакты. Например, понятную для поддержки документацию. Эта документация дает возможность всем заинтересованным лицам сформировать свое представление о продукте и сценариях пользовательского поведения, которые должны быть реализованы в ходе итераций разработки. С BDD-подходом мы также снижаем порог входа в проект новых участников.

— Многие считают, что BDD можно рассматривать как переход от основанной на unit-тестах разработки к разработке, основанной на интеграционном тестировании. А как ситуация обстоит на самом деле?

Юлия Ковалева: Мы провели такой эксперимент. Попытка использовать BDD-инструменты для написания unit-тестов успехом не увенчалась, через какое-то время разработчик отказался писать поведенческие тесты. Когда мы говорим про автоматизацию, BDD идеально вливается в эту историю, но применять такой подход к модульному тестированию не стоило, пользы нам это не принесло. При интеграционном тестировании польза от BDD будет значительной — здесь мы смотрим на весь продукт в целом.

— В каких случаях для тестирования применяется BDD подход и чем он лучше более традиционного TDD?

Анна Чернышева: BDD в основном используется для проверки взаимодействия разных компонентов системы, это, как уже было сказано, уровень интеграционного тестирования — оно поведенческое и проверяет различные бизнес-кейсы. TDD используется для разработки через модульное тестирование непосредственно программистами, которые пишут код через этот подход. В общем, если по-простому, TDD проверяет исключительно модули, а BDD — пользовательские сценарии.

— Зачем потребовались новые BDD-фреймворки, не проще оформить BDD в набор рекомендаций по написанию TDD и использовать существующие?

Юлия Ковалева: BDD появился, чтобы сделать команду разработки ближе к бизнесу, организовать диалог между бизнесом, разработчиками и тестировщиками. При TDD-подходе нужно писать тесты на формальных языках программирования. Тесты и код лежат в одном месте, их достаточно сложно поддерживать, а уж тестировщик или бизнес-аналитик едва ли полезет смотреть, что мы там написали. Два наиболее популярных BDD фреймворка — JBehave и Cucumber. Как их правильно применять, мы расскажем на ближайшей конференции Гейзенбаг 2017 Piter.

Анна Чернышева: Эта путаница между TDD и BDD пошла от общей для разных подходов идеи: сначала пишутся тесты, а потом идет разработка. При всех сходствах они предназначены для разных целей и требуют применения различных инструментов.

— То есть BDD нельзя считать расширением TDD?

Анна Чернышева: Да, мы считаем именно так. Вместо тестовой модели у нас есть пользовательские сценарии, которые мы автоматизируем в одном спринте с разработкой. Более того, в «Альфа-Лаборатории» BDD-подход органично внедрен и в инженерные практики.

— QA считают женской профессией. Как вы полагаете, с чем это связано и насколько соответствует действительности? Есть ли вообще в IT понятие женской и мужской профессии?

Анна Чернышева: Сейчас от этого стараются уйти, но женское мышление более абстрактно, а мужчинам важны конкретные детали — это, конечно, все индивидуально, но если брать «Альфа-Лабораторию», то девушек-тестировщиков в процентном соотношении несколько больше. Может, тестирование требует творческого подхода, а девушки — более творческие натуры?

Юлия Ковалева: Можно провести эксперимент и подсчитать, сколько девушек и мужчин придут на Гейзенбаг.

На Гейзенбаг 2017 Юлия Ковалева и Анна Чернышева сравнят Cucumber и JBehave. У какого BDD-фреймворка больше возможностей? Как их правильно «готовить» и с какими трудностями придется столкнуться во время тестирования — состязание ведущих разработчиков автотестов «Альфа-Лаборатории» пройдет в лучших традициях Mortal Kombat.

BDD Girls Battle: Cucumber vs. JBehave

Полная программа конференции доступна на сайте.

TDD — разработка через тестирование

TDD, test-driven development или процесс разработки через тестирование — это методология разработки программного обеспечения, которая основывается на повторении коротких циклов разработки: изначально пишется тест, покрывающий желаемое изменение, затем пишется программный код, который реализует желаемое поведение системы и позволит пройти написанный тест, а затем проводится рефакторинг написанного кода с постоянной проверкой прохождения всех тестов.

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

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

Цикл разработки по TDD

  • Добавить тест для новой (еще не реализованной) функциональности или для воспроизведения существующего бага
  • Запустить все тесты и убедиться, что новый тест не проходит
  • Написать код, который обеспечит прохождение теста: 
  • Запустить тесты и убедиться, что они все прошли успешно: прохождение нового теста подтверждает реализацию нового функционала или исправление существующей ошибки, а прохождение остальных позволяет удостовериться, что ранее реализованный функционал работает по-прежнему корректно. 
  • Заняться рефакторингом и оптимизацией — улучшение сопровождаемости и быстродействия целесообразно осуществлять уже после того, как получилось добиться проверяемой работоспособности
  • Перезапустить тесты и убедиться, что они все ещё проходят успешно
  • Повторить цикл

Эта методология позволяет добиться создания пригодного для автоматического тестирования приложения и очень хорошего покрытия кода тестами, так как ТЗ переводится на язык автоматических тестов, то есть всё, что программа должна делать, проверяется. Также TDD часто упрощает программную реализацию: так как исключается избыточность — если компонент проходит тест, то он считается готовым. Если же существующие тесты проходят, но работает компонент не так, как ожидается, то это значит, что тесты пока не отражают всех требований и это повод добавить новые тесты.

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

Разработка через приемочные тесты (ATDD). Что это такое, и с чем его едят / Habr

Разработка через тестирование (TDD) – отличный способ повысить качество и надежность кода. Этот же подход может быть распространен и на разработку требований. Он называется «Разработка через приемочные тесты» – acceptance test driven development (ATDD). Сначала я присматривался к этому подходу, потом пробовал применить, потом долго тюнинговал, чтобы приспособить его под мои нужды, и теперь хочу поделиться мыслями. И для себя еще раз разложить все по полочкам.

В этой статье я расскажу небольшое введение в тему. Пример будет совсем простой и скорее для иллюстрации. А в следующей статье постараюсь поделиться историей, как я применял ATDD на практике при разработке настоящей фичи в реальном продукте.


Когда я работал программистом в аутсорс компании на один банк, то мне приходилось изучать спецификации требований и оценивать трудоемкость задач. Оценивать нужно было как можно точнее, мы работали по модели оплаты за проект (Fixed Price), и все промахи в сроках были на нашей стороне и не оплачивались. Каждый раз, когда я читал спецификации, мне было все понятно, я не замечал в них нелогичные моменты, упущения, странности. Но как только начиналась разработка, то все косяки требований вылезали наружу, и было удивительно, как я их пропустил в начале. Несмотря на все усилия, я так и не мог придумать способ, как читать спецификации и находить в них проблемы до реализации.

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

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


Acceptance test driven development (ATDD) является развитием идеи test driven development (TDD). Общий смысл в том, что прежде чем что-то делать, надо придумать критерий выполненной работы и критерий того, что работа сделана правильно. Почему это важно? Потому что эти критерии позволяют на самом раннем этапе понять, что именно требуется сделать, как это сделать, что именно считать хорошим результатом. Т.е. не выяснять детали по ходу дела, строя прототипы, а сразу приближаться к цели, так как цель уже определена, причем вполне формально.

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

Несмотря на общее название, этот подход относится ко вполне определенной части процесса – той, где происходит разработка требований и их формализация в спецификации. В данном процессе часто участвуют люди как со стороны бизнеса, так и с технической стороны, т.е. люди, обладающие разными компетенциями и взглядами на мир. Заказчики на интуитивном уровне понимают, что именно они хотят видеть в продукте, но сформулировать и перечислить требования кратко (и полно) могут далеко не все. Разработчики (представители исполнителя), в свою очередь, часто не знают, что именно забыл рассказать заказчик, и как это выяснить.
Для решения этих задач используется фреймворк Given – When – Then.


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

В юнит тестах используется шаблон Arrange – Act – Assert (AAA). Это означает, что в тестах должны быть явные части, отвечающие за подготовку данных — arrange, само действие, результат которого надо проверить – act, и собственно проверка, что реальность совпала с ожиданиями – assert. Для приемочных тестов используется подход Given – When – Then (GWT). Суть та же, только с другого ракурса.


  • Given описывает что «дано», т.е. состояние системы в начальный момент времени
  • When задает непосредственно триггер, который должен привести к результату. Чаще всего это какое-то действие пользователя.
  • Then определяет результат этого действия, т.е. является критерием приемки.

Given часть может содержать в себе как одно, так и набор состояний. В случае, когда их несколько, эти состояния должны читаться через «И». Объединять какие-то состояния через «ИЛИ» можно, но тогда это будут два разных теста. Такое возможно для упрощения записи. Я рекомендую избегать этого до того момента, как все возможные комбинации состояний не будут описаны. Тогда можно быть уверенным, что ничего не забыто и слить несколько тестов в один для упрощения чтения и понимания. Это же справедливо и для Then — исходов, которые надо проверить может быть несколько. When должен быть один. Взаимовлияния триггеров лучше избегать.

GWT тесты вполне можно читать вслух: «Пусть (given) A и B, и C. Когда (when) случается D, то (then) получается E и F.». Их вполне можно использовать для документации или формулирования требований. Когда я говорю «читать», я не имею ввиду, что именно так они и должны быть записаны. В реальности такие тесты получаются очень масштабными. Если их записать простым текстом, то потом взглянуть на них системно очень тяжело. А без системы легко пропустить какие-нибудь важные сценарии.

Очень важный момент: формат записи нужно выбирать тот, который наиболее подходит к вашей задаче, с которым удобнее работать. Никаких ограничений тут нет. Given, when, then — это общая структура записи, то есть то, что обязательно должно быть в тесте, а непосредственное представление может быть любым – хоть предложения, хоть таблицы, хоть диаграммы.

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


Для примера возьмем что-нибудь простое и понятное, например, светофор. Как можно описать требования к разработке светофора с помощью GWT нотации? Для начала нужно понять, что именно в светофоре можно назвать Given, что является When, а что Then.

За состояние светофора можно принять информацию о том, какая секция сейчас горит. Светофор переключается (меняет состояние) через какие-то промежутки времени. Значит триггером является таймер, точнее, срабатывание таймера. Результатом срабатывания триггера является переход в одно из состояний. Т.е. можно считать, что в примере со светофором Given и Then – один и тот же набор:


  1. Горит красный
  2. Горит красный и желтый
  3. Горит зеленый
  4. Зеленый мигает
  5. Горит желтый

Опишем поведение светофора в нотации GWT:


  1. Пусть горит Красный. Когда таймер срабатывает, тогда светофор переключается в режим одновременного Красного и Желтого.
  2. Пусть горит Красный и Желтый. Когда триггер срабатывает, тогда светофор переключается в Зеленый.
  3. Пусть горит Зеленый. Когда триггер срабатывает, тогда светофор переключается в Зеленый мигающий.
  4. Пусть Зеленый мигает. Когда триггер срабатывает, тогда светофор переключается в Желтый.
  5. Пусть горит Желтый. Когда триггер срабатывает, тогда светофор переключается в Красный.

Вот 5 сценариев, прочитав которые, можно понять, как работает светофор. Естественно, у светофора есть еще куча режимов, например, режим желтого мигающего (когда он неисправен), или ручной режим управления (например, в случае ДТП) и т.д. Но не будем усложнять иллюстрацию.

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


Given When Then
1 Красный Таймер Красный + Желтый
2 Красный + Желтый Таймер Зеленый
3 Зеленый Таймер Зеленый мигающий
4 Зеленый мигающий Таймер Желтый
5 Желтый Таймер Красный

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


Нотация Given — When — Then структурирует процесс составления тестов и дает уверенность в том, что тесты описывают все аспекты поведения системы. Не нужно сидеть и постоянно спрашивать себя: «А какой сценарий я еще не описал?».
Итак, алгоритм такой:


  1. Определить все состояния, которые могут быть заданы, т.е. все Given.
  2. Определить все триггеры, т.е. When.
  3. Определить все Then, что именно может произойти.
  4. Теперь эти списки надо комбинаторно перемножить.
  5. В результате получается набор тестов.

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


Как уже было сказано, подобный подход, несмотря на свою избыточность, дает уверенность в том, что ни один из сценариев не будет пропущен. Это, пожалуй, главное преимущество такой формализации. Зачастую бизнес-пользователь видит процесс только в общих чертах и ему не видны детали. Я уверен, что вы постоянно слышите от заказчика или даже аналитика фразы типа: «Нам нужна такая-то фича, я все придумал, вот, смотри картинку», или «Тут нам нужна такая-то кнопка, у нас уже есть похожая функциональность в другом месте, сделай как там». Если до того, как начать разработку, сесть и прикинуть возможные варианты развития событий, то сразу всплывет очень много деталей, в которых, как известно, и кроется дьявол.

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

Помимо анализа требований с целью разработки решения, GWT сценарии можно применять и для сбора требований. Предположим, что есть какая-то функциональная область и человек, который в ней разбирается, но время на общение с ним очень ограничено. Если подготовиться заранее и разобрать сценарии с помощью GWT фреймворка, то на самом интервью нужно будет узнать только то, что мы ничего не забыли из раздела Given, When и уточнить, что именно должно быть в разделе Then.

Есть специальные инструменты для автоматизации GWT сценариев, записанных в том числе и на естественных языках. Пример — cucumber. Я с ними не работал, поэтому ничего кроме факта их существования рассказать не могу.


Обратная сторона мощности GWT — избыточность. Предположим, что вы определили N штук given, M штук when и K штук then. В худшем случае количество тестов будет огромным – N M K. И с этим надо как-то жить. Это верхняя оценка сложности; в реальности далеко не все эти сценарии будут осуществимы, а часть из них будет дублировать друг друга, а еще часть можно пропустить ввиду низкого приоритета или очевидности.

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

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


Немного исторической справки

Если верить Википедии, то идея формулировать спецификации через конкретные сценарии была впервые описана Ward Cunningham в 1996 году, а сам термин specification by example ввел Martin Fowler в 2004 году. Дальнейшее развитие идеи формулируется в книге «Bridging the Communication Gap: Specification by Example and Agile Acceptance Testing» от Gojko Adzic 2009 года. В 2011 он же выпустил еще одну книгу на эту тему: «Specification by Example: How Successful Teams Deliver the Right Software». Рекомендую эти книги для обращения к первоисточнику.

TDD — это как сноубординг / Habr


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

«Я не использую эту методологию (TDD) из-за того, что главный для меня вопрос остается без ответа. Я знаю, что использование TDD уменьшает количество багов, но что насчет времени, необходимого при работе по этой методологии?
Я хотел бы знать как изменяется время на разработку корпоративного приложения с использованием TDD — уменьшается, увеличивается или остается неизменным.
Надеюсь, вы сможете ответить, так как TDD и BDD меня очень интересуют.»


Первая и самая важная вещь, на которую я хотел бы обратить внимание, — ошибочное представление о TDD как об инструменте для уменьшения количества багов. Хотя это и является одним из «побочных эффектов» TDD, это не основной мотив для его использования. Главный довод в пользу TDD — последовательная разработка дизайна каждого компонента вашей системы посредством написания теста для него.

Что касается следующего вопроса:

«Я хотел бы знать как изменяется время на разработку корпоративного приложения с использованием TDD — уменьшается, увеличивается или остается неизменным.»

Когда вы впервые решаетесь забраться на гору и рискнуть прокатиться на сноуборде, вы забираетесь туда с мечтой в голове о том как будете рассекать несколько минут и выделывать трюки круче, чем в SSX Tricky. Но после пары падений вы чувствуете увесистый пинок реальности и осознаете, что, похоже, это займет немного больше времени, прежде чем вы научитесь хотя бы просто съезжать с горы без всякого блеска и очарования. Если вы благоразумны, то потратите время на занятия с квалифицированным инструктором, который за день обучит вас основам и поставит технику. На базе занятий с интруктором вы сможете расти как сноубордист следующие пару месяцев, и, скорее всего, не нахватаетесь вредных привычек, которыми могли бы обзавестись, попытайтесь вы научиться самостоятельно.

Как в сноубординге (да и в любом другом новом деле, которое вы решили освоить), в начале нужно потратить время на обучение. Большинство людей, решивших окунуться в TDD, осознают, что в их знаниях много пробелов, которые могут создать проблемы в этом новом мире. Даже если со знаниями у вас все в порядке, вы должны осознавать, что в любом случае первое время вы будете тренировать свой мозг решать программные проблемы непривычным способом. На самом деле, это одно из самых больших препятствий при переходе на TDD — выработка нового стиля программирования, который заставляет вас думать о маленьких шагах и разрабатывать API, как будто он уже написан. Хороший способ начать практиковать TDD — использовать state-based тестирование (от перев. — подход, при котором проверяется состояние объекта после прохождения теста). Как только вы набьете руку на state-based тестировании, вы сможете сочетать его с interaction-based тестированием (от перев. — подход, при котором тестируется взаимодействие объектов, поведение методов, последовательность их вызовов и т.д.).

Начав использовать TDD, вы можете обнаружить, что работаете медленнее, чем обычно, — это в основном из-за того, что вы будете работать вне «зоны комфорта», чувствуя себя неестественно. После того как вы ощутите, что написание тестов стало естественной частью рабочего процесса, что вам больше не нужно думать об использовании TDD при работе над проектом, так как TDD влилось в вашу работу, вы поймаете ваш «АГА!» момент с TDD.

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

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

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

Для тех из вас, кто всерьез рассматривает использование TDD, но еще не начал, — возможно, сейчас хороший сезон, чтобы попробовать взобраться на холм!

Почему изучать TDD трудно и что с этим делать. Часть 2 / Habr

Продолжение. Начало здесь.

Как все это использовать?


Хороший вопрос. Мы остановились на том, что TDD помогает четко определить границы текущей задачи, дает простой способ одновременной работы с мелкими деталями, относящимися к проблеме, и предоставляет быструю обратную связь с кодом, сообщая, насколько удачно получившееся решение. Именно эти факты помогут нам преодолеть трудности в изучении этой техники.

Какие тесты писать?


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

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

Посмотрим на более выраженный случай. Скажем, мы имеем совершенно новый проект. Значит, мы даже еще не знаем, какие классы будут нам нужны. Как использовать TDD, чтобы что-то узнать о будущей структуре классов, когда еще нет вообще ни одного класса? Напишем тест, описывающий разрабатываемый функционал (или сценарий). Это будет скорее приемочный, нежели юнит-тест. Нигде не написано, что TDD основывается исключительно на юнит-тестах, но большинство новичков думают, что процесс основан именно на них, и из этого вытекает дальнейшее непонимание. Так вот, такой подход позволяет создать базовый скелет приложения, который в дальнейшем будет развиваться и обрастать плотью. Тесты позволят оценивать, насколько хорошо мы сумели разделить требуемый функционал на контролируемые, тестируемые абстракции. Вдобавок мы получим отличный интерфейс к системе, который пригодится в будущем при создании интеграционных тестов. Эта книга является отличным источником примеров (по ссылке — книга Growing Object-Oriented Software Guided by Tests, посвященная так называемой Лондонской школе TDD, придерживающейся подхода «снаружи-внутрь» с интенсивным использованием моков для еще нереализованных классов — прим. перев.)

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

Но ты только что написал код, не создав сначала теста!


Запросто. TDD обеспечивает обратную связь с кодом. Когда вы уже знаете, что именно нужно (например, если работа заключается в дополнении мелкими частями готовой архитектуры), та часть TDD, что отвечает за проектирование, не дает много пользы. Единственное преимущество предварительного написания тестов здесь в том, что можно убедиться, что изначально они не срабатывают; это способ протестировать сами тесты. В этом случае подход «сначала тесты» является техникой создания кода, покрытого тестами, а не проектирования.

Чем меньше неизвестных, тем меньше нужно тестов, чтобы их найти и тем большими шагами можно двигаться.

Вот опять — используя паттерн MVVM, ты создал представление без предварительного теста!


Мы уже узнали, что TDD ограничивает текущую задачу, так же как и дает возможность понять, насколько хорошо ее решение. Конечно, TDD – далеко не единственный способ получить как первое, так и второе. Технологии создания представлений (WPF, MVC-фреймворки, GTK, WebForms и т.д.) сами по себе налагают существенные ограничения на код в части взаимодействия с UI.

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

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

Примечание: Я столкнулся с трудностями при попытке воссоздать паттерны представления при помощи следования принципам TDD: как можно, используя только тесты, в итоге получить MVP, MVVM и т.д? Думаю, что это сложно из-за того, что с одной стороны, у нас есть абсолютно реальный UI-инструментарий, а с другой — только воображение и тесты. Существуют факторы, которые не вытекают из тестов, но являются такими же важными источниками информации. Если кому-то удалось получить в чистом виде паттерн отделения представления только при помощи TDD, пожалуйста, дайте знать.

Я застрял! Застрял в тестах и не знаю, что делать…


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

Если процесс не дает необходимой информации, вернитесь назад и попробуйте сделать иначе. Обсудите идеи с коллегами. Попробуйте поработать, используя покрытие тестами более высокого уровня абстракции. Просто попробуйте другие подходы — часто проще испытать в деле три разных решения, чем теоретически выявить одно самое лучшее заранее. TDD является хорошим, но не единственным средством ограничить проблему и выйти на решение. Сделайте то, что необходимо прямо сейчас, чтобы решить задачу, но не забудьте вернуться и попытаться понять причину остановки позже.

На заметку: Если не удалось решить проблему с помощью TDD, сделайте пометку об этом перед тем, как пробовать что-то еще. Вам важно понять, были ли эта задача в принципе не решаема с помощью TDD, или вам просто не хватило знаний. Уловить разницу не получится, если сдаться слишком быстро. Лично меня следование этому принципу привело в свое время к открытию таких штук как мокирование, IoC-контейнеры, соглашения, BDD и т.д. (и я все равно еще далек от полного отсутствия пробелов в знаниях).

TDD? BDD? ATDD? Что именно мне нужно?


Вы можете заметить, что использование TDD специфично для каждой задачи (и для каждого разработчика). Разнообразные задачи создают разные проблемы проектирования и требуют разнообразных видов обратной связи. Это значит, что нам следует задействовать TDD разными способами, чтобы получать разные оценки разрабатываемого кода и использовать их в развитии архитектуры. Такими способами могут быть «сверху внизу» или «снизу вверх». Иногда мы можем опираться большей частью на приемочные тесты. Иногда лучше использовать большое количество модульных тестов. Некоторые проблемы хорошо решаются с помощью тестов «от края до края», с использованием реальных внешних баз данных или сервисов (у этого подхода есть свои тонкости, но если он дает необходимую обратную связь и обеспечивает развитие архитектуры — используйте его).

В общем, здесь нет единственного верного ответа. Главная цель — получение обратной связи с кодом, над которым идет работа в данный момент.

Заключение


Хотя инструкции процесса TDD просты, сама техника таковой не является. Инструкции обеспечивают лишь самую минимальную помощь на старте, в то время как шумиха вокруг TDD формирует совершенно нереалистичные ожидания.

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

Поняв это, мы можем преднамеренно использовать TDD для получения обратной связи с разрабатываемым кодом и использования этой информации для решения задач проектирования. Мы не ограничиваемся только юнит-тестами и постоянно переключаемся между уровнями абстракции тестов (чего совсем нет в демонстрационных примерах использования TDD). И мы запросто обходимся без TDD там, где есть другие способы получения обратной связи с кодом, такие как фреймворки отделения UI или технологии хранения данных.

Мы так же не будем впадать в отчаяние в случае невозможности применения правил TTD, так как знаем, что эта техника всего лишь является инструментом проектирования, и всегда найдутся пробелы в знаниях или недостаток опыта, которые придется заполнить перед тем, как станет возможным решать новые задачи. То, что TDD является отличным индикатором недостатка знаний, дает шанс пополнить их запас прямо сейчас. Это намного лучше блаженного забвения, приводящего, в конце концов, к срыву сроков сдачи проекта, или, что еще хуже, бесконечному повторению одних и тех же ошибок.

Проектировать всегда трудно. TDD позволяет сконцентрироваться на дизайне и понять, как сделать его лучше.

Надеюсь, эти размышления откроют Вам иной взгляд на TDD и сделают процесс изучения этой техники более простым. TDD стоит затраченных усилий. Желаю удачи!

Радченко Глеб Игоревич



Научные интересы

  • Грид-вычисления.
  • Облачные вычисления.
  • Распределенные вычислительные системы.

Публикации

Проекты

  1. Проект Erasmus+ PWs@PhD. Основная цель проекта PWs@PhD – поддержка развития, модернизации, интернационализации высшего образования, а именно исследовательской составляющей европейского образования уровня PhD, содействие созданию новых PhD-программ в странах-партнерах в области программной инженерии.
  2. Сервисно-ориентированный подход к использованию проблемно-ориентированных пакетов в распределенных и грид-средах (проект DiVTB).
  3. Параллельная реализация нейросетевого алгоритма распознавания раздельной речи (Часть 1, Часть 2, Часть 3).

Новости

  • [2013-12-25]  Обновления страниц курсов:
  • [2013-12-17]  Обновления страниц курсов:
  • [2013-11-28]  Обновления страниц курсов:

 

  • [2013-11-07]  Размещены слайды презентаций:
  • [2013-10-26] Размещены слайды презентаций:
  • [2013-06-03]  Размещены слайды презентаций:

[Архив новостей]

Ссылки

  • Mendeley — система для каталогизации и управления библиографией. Встраивается в Microsoft Word, позволяя автоматизировать процесс управления списками литературы при подготовке статей. Поддерживает множество форматов оформления библиографических ссылок, включая ГОСТ-7.0.5-2008.
  • Memsource — операционная среда для выполнения письменных переводов, включающая базы памяти переводов, встроенный машинный перевод, модуль управления терминологией, а также текстовый редактор MemSource Editor. Может импортировать и экспортировать документы всех стандартных форматов, включая Word и PowerPoint.

Мой профиль

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *