Tdd это что – TDD/BDD в чем разница и для каких видов модулей стоит использовать? — Хабр Q&A

alexxlab
alexxlab
06.09.2020

Содержание

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

Разработка через тестирование (англ. 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-объекты и интеграционные тесты[править | править код]

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

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

  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.

Как, используя TDD, сократить время разработки

Автор

Павел

Павел

Ведущий веб-разработчик

TDD или Test Driven Development — это разработка через тесты: сначала пишутся интеграционные тесты, в которых детально описан будущий функционал. А новые функции разрабатываются на основе этих тестов.

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

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

Зачем использовать

  1. Ускоряет разработку. Пока разработчики привыкают к написанию тестов, работа будет идти медленнее. Когда привыкнут, разработка ускоряется;
  2. Высокое покрытие функционала тестами. На каждый функционал я пишу 20-25 тестов, чтобы охватить все возможные варианты его использования;
  3. Меньше времени на отладку. Обычно отладка начинается, когда основной функционал уже сделан. Здесь мы отлаживаем заранее концепцию функционала, пока пишем тесты.

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

Путь любого кода при разработке по TDD проходит 3 стадии:

  • Красная стадия: пишем тесты. Первый прогон тестов будет всегда возвращать ошибку, потому что интерфейс, который они проверяют, не готов. Каждый тест должен переживать в своей жизни хоть одно падение. Как минимум, сразу после написания. Как говорят адепты TDD: «Я не могу доверять тесту, который никогда не видел упавшим». Упавший тест обратит на себя внимание, и вы задумаетесь о правильности реализации функционала или самого теста.
  • Зеленая стадия: разрабатываем функционал и заново проверяем. Наша задача — добиться того, чтобы тест стал зеленым. Так мы понимаем, что функционал разработан верно. Важно, чтобы тест минимум один раз упал и минимум один раз прошёл. Легко ошибиться и написать тест, который всегда падает или всегда проходит. А вот тест, который и падал и проходил, явно проверяет какую-то логику. После этого можно считать, что функционал работает верно.
  • Рефакторинг. Рефакторим конкретную задачу, потому что уже уверены, что у нас рабочий код теста. Просто пишем логичный и чистый код для нашего проекта.
TDD.png

Где это работает

Выбор TDD зависит от:

  1. Команды и человека, который ставит и принимает задачи (назовем его заказчик).
  2. Внутренних процессов на проекте. Например, в команде не используется непрерывная интеграция. Здесь использовать TDD не стоит, потому что разработчик не будет каждый раз запускать тесты перед тем, как залить код в репозиторий. Ему станет лень в итоге.

Есть критерии, по которым вы сможете определить, что TDD сработает:

  • В проекте чётко описана структура интерфейсов, например, используются JSON-API или REST-API. Наша задача — ускорить разработку продукта. Когда есть четкая структура, программист не придумывает структуру сам. Если четкости нет, программист продумывает структуру интерфейсов, а его видение может не совпасть с тем, что в голове у заказчика. Поэтому, если на проекте есть продуманная структура API, пишите тесты до кода;
  • В проекте однотипный подход к реализации интерфейсов, т.е. логика и порядок работы над разными сущностями одинаков или сильно похож. Перед написанием тестов мы организуем в тестовом окружении требуемые данные. Если на проекте есть определённый подход к реализации интерфейсов, вы можете спокойно применять один шаблон и экономить время на написание тестов. Если единого подхода в разработке нет, придётся для каждого теста создавать собственные условия перед запуском. На это уйдет столько же времени, сколько при обычной разработке, поэтому использование TDD не принесет пользы;
  • Задачи ставятся в стиле: одна подзадача — один тест. Обычно такие задачи ставит заказчик-технарь. Заведите систему: внутри задачи каждая подзадача — это один тест. Тогда разработчики спокойно пишут тесты, которые описывают подзадачи, потом реализацию, и тесты постепенно становятся зелёными. Если в каждой подзадаче можно вычленить несколько тестов, процесс не выиграет по времени по сравнению с обычной разработкой;
  • Есть высокоуровневый тестовый фреймворк с поддержкой интеграционных тестов на технологии, которая используется на проекте. Альтернативный вариант: абстрагированный от реализации тестовый фреймворк на другой технологии. Весь мой TDD — это Ruby проекты. Здесь я использую средства тестирования RSpec, Minitest, Capybara. Тесты должны быть абстрагированы от реализации, поэтому не важно, какой инструмент вы выберете. RSpec + Capybara можно применять на любом проекте, который имеет HTTP-интерфейс. Лучше, если такой инструмент есть в вашем языке. Так программисту не придется переключать контекст во время разработки;
  • В команде нет отдельных тестировщиков. Если в команде есть тестировщики, тесты должны писать именно они. В итоге теряется главное преимущество TDD — «ментальная связь программиста с функционалом», ведь тесты пишет другой человек. Чтобы TDD принес пользу, тесты должен писать программист.

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

Где это не работает

TDD — не панацея и не универсальное решение. Для каких-то случаев он подходит, а где-то может навредить. Если TDD не подходит для проекта, он помешает и замедлит разработку. В итоге разработчики начинают считать, что:

  1. TDD применим только для продуктовых компаний;
  2. У нас нет подходящего стека технологий для TDD;
  3. Методология не работает;
  4. Можно сделать только на backend;
  5. В нашей предметной области не работает.

Чтобы такого не произошло, «приценитесь» к TDD перед внедрением. Не используйте его, если:

  1. Задачи ставятся без должной проработки. Задачи ставит не технарь и отдает продумывание сценариев программисту. В таких случаях писать тесты в начале — риск потратить на разработку больше времени. Например, есть задача «нужна форма регистрации. Сначала много полей, потом аккаунты в соцсетях». Разработчик сделал страницу, на которой сверху располагались поля, а снизу были кнопки соцсетей. Оказалось, что заказчик хотел трехступенчатую регистрацию: 1 — фамилия, имя; 2 — аккаунты соцсетей; 3 — дополнительная информация. Нужно переделывать и функционал, и тесты — в два раза больше бесполезной работы.

    Так увидел заказчик Регистрация. Шаг 1Регистрация. Шаг 2Регистрация. Шаг 3

    Так подумал разработчик
    Регистрация
    Здесь лучше писать тесты после утверждения заказчиком функционала;

  2. Непроработанная заранее бизнес-логика. Разработчик до начала разработки точно не знает, как будет работать функционал. В моей практике было 3 BI системы для внутренних нужд клиентов. Во всех проектах требования к ним менялись ежедневно. Заказчики не всегда понимали, какие именно метрики нужны, поэтому требования уточнялись после разработки. Функционал нужно делать максимально быстро, чтобы заказчик видел что не так и предлагал изменения. Если пытаться писать сначала тесты, то заказчик увидит функционал позже, и сроки будут постоянно сдвигаться;
  3. Сложные вычисления или большие нагрузки. Если в проекте сложные вычисления, результат этих вычислений сложно предсказать заранее на этапе написания теста. Например, вы собираете и анализируете тысячи данных о пользователях, чтобы выяснить, какие услуги популярнее. Результат в тестах нужен, потому что с ним будут сравнивать реализацию.

Применять или не применять TDD зависит от ответственного за проект и ценностей команды. Для многих писать тесты до разработки — это разрыв шаблона. Как так? На самом деле, нет ничего страшного.

Напомню:

  • тесты должны покрывать не код, а функционал;
  • тесты должны быть абстрагированы от реализации;
  • тесты должны исполняться в своём собственном окружении.

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

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

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

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



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

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

Публикации

Проекты

  1. Проект Erasmus+ [email protected] Основная цель проекта [email protected] – поддержка развития, модернизации, интернационализации высшего образования, а именно исследовательской составляющей европейского образования уровня 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.

Мой профиль

 

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

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

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

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

Эта методология позволяет добиться создания пригодного для автоматического тестирования приложения и очень хорошего покрытия кода тестами, так как ТЗ переводится на язык автоматических тестов, то есть всё, что программа должна делать, проверяется. Также 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 трудно и что с этим делать. Часть 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 стоит затраченных усилий. Желаю удачи!

Разное

Отправить ответ

avatar
  Подписаться  
Уведомление о