26 февраля 2009 в 10:16

Экстремальное программирование, знакомство с Behavior Driven Development и RSpec

Теория


Для начала, давайте разберемся, что же такое Behavior Driven Development(в дальнейшем BDD) и чем данная техника отличается от Test-Driven Development(в дальнейшем TDD)

Разрабо́тка че́рез тести́рование (англ. test-driven development) — техника программирования, при которой модульные тесты для программы или её фрагмента пишутся до самой программы (англ. test-first development) и, по существу, управляют её разработкой. Является одной из основных практик экстремального программирования.

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

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

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

На самом деле большинство из нас уже и так думает подобным образом. Смотрите:

Фрэнк: Что такое стек?

Линда: Это структура данных, хранящая объекты в порядке «первым вошел, последним вышел» или «последним вошел, первым вышел». Обычно у этой структуры есть API с такими методами, как push() и pop(). Иногда присутствует метод peek().

Фрэнк: Что делает метод push()?

Линда: Метод push() принимает входной объект, например, foo и помещает его во внутренний контейнер, например, массив. Метод push() обычно ничего не возвращает.

Фрэнк: Что будет, если передать методу push() два объекта, например, сначала foo, а потом bar?

Линда: Второй объект bar должен оказаться наверху концептуального стека, содержащего по крайней мере два объекта, так что при вызове метода pop() объект bar должен быть извлечен первым, до первого объекта foo. Если метод pop() вызвать еще раз, должен быть возвращен объект foo и стек должен стать пустым (предполагается, что в нем ничего не было до того, как мы добавили эти два объекта).

Фрэнк: Так метод pop() удаляет самый последний элемент, добавленный в стек?

Линда: Да, метод pop() должен удалить верхний элемент, при этом предполагается, что в стеке есть элементы, чтобы их удалять. Метод peek() работает точно также, но при этом объект не удаляется. Метод peek() должен оставить верхний элемент в стеке.

Фрэнк: Что будет, если вызвать метод pop(), когда в стек еще ничего не было добавлено?

Линда: Метод pop() должен выдать исключение, показывающее, что в стек еще ничего не добавлялось.

Фрэнк: Что будет, если выполнить команду push() null?

Линда: Стек должен выдать исключение, так как null не является допустимым значением для метода push().

Можно ли выделить что-нибудь особенное в этом диалоге, кроме того, что Фрэнк не силен в структурах данных? Нигде не использовалось слово «тестирование». Однако слово «должен» проскальзывало регулярно и звучало довольно естественно.

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

Практика


RSpec — это BDD framework для Ruby

Установка:
[sudo] gem install rspec
либо
git clone git://github.com/dchelimsky/rspec.git
cd rspec
rake gem
rake install_gem
Давайте представим, что вашим заказчиком является банк. Проиллюстрируем, еще раз, диалог с заказчиком, в стиле BDD:
Вы: Пожалуйста, опишите, каким должен быть счет после его создания?
Заказчик: Счет должен иметь баланс равный $0.

Вот как мы опишем данный разговор в RSpec:
describe Account, " when first created" do

 before do
  @account = Account.new
 end

 it "should have a balance of $0" do
  @account.balance.should eql(Money.new(0, :dollars))
 end
 
 after do
  @account = nil
 end

end

* This source code was highlighted with Source Code Highlighter.

Если выполнить этот пример, RSpec может вернуть подобное описание:
Account, when first created
— should have a balance of $0

Пример того, как вы можете использовать #before и/или #after блоки, в вашем коде:
describe Thing do
 before(:all) do
  # This is run once and only once, before all of the examples
  # and before any before(:each) blocks.
 end

 before(:each) do
  # This is run before each example.
 end

 before do
  # :each is the default, so this is the same as before(:each)
 end

 it "should do stuff" do
  ...
 end

 it "should do more stuff" do
  ...
 end

 after(:each) do
  # this is before each example
 end
 
 after do
  # :each is the default, so this is the same as after(:each)
 end

 after(:all) do
  # this is run once and only once after all of the examples
  # and after any after(:each) blocks
 end
 
end

* This source code was highlighted with Source Code Highlighter.

В заключение


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

Оффициальная документация (англ.)
Хороший пошаговый мануал (англ.)
Разработка через тестирование (рус.)
Знакомство с Behavior Driven Development (BDD) (рус.)
Определение BDD (англ.)
Кратки мануал по RSpec (англ.)
Определение TDD (рус.)
Абушаев Arion Денис @Arion
карма
53,5
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (36)

  • +4
    Положительный отзыв:)
    • +1
      Одобрительное кивание.
  • 0
    поддерживаю
  • +6
    Честно говоря, статья так и не раскрыла разницу между понятиями «тест» и «должен», функциональное тестирование и unit-тестирование понятия иногда расходящиеся, иногда сходящиеся.

    Почему бы «должен» не отнести к «метод А должен возвращать 1, если передали 0», реализация проверки есть самый настоящий «тест».

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

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

    Более, того, на мой взгляд необосновано замолчено грандиозное требование TDD к использованию: код, который вы разрабатываете, должен предполагать автоматическое тестирование (!) Данный принцип позволяет проектировать архитектуру приложения исходя из возможности его тестирования автоматическими unit-тестами.

    Аббревиатура BDD мне не очень известна, но известен инструмент FIT (и его аналоги), есть некоторые обзоры тут: lobasev.ru/search/label/FIT

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

    Подчеркну, что TDD и BDD это не соперники, а скорее совершенно разные методики, которые совместно необходимо применять в проектах.
    • 0
      «Основная цель: привлечение к тестированию пользователей продукта, где они могут создавать тесты на бизнес-языке и проверять работу приложения в автоматическом режиме. Насколько я понял из Вашего примера — это есть одно и то же.»

      Нет, в BDD тесты пишет сам программист, так же как и в TDD. А то что синтаксис библиотеки RSpec выглядит как BNL — это синтаксический сахар Ruby.
      И все-таки BDD и TDD соперники, нет смысла писать юнит тесты если есть спецификации. BDD можно рассматривать как новый слой абстракции над юнит тестами. Если там мы просто проверяем равенства и т.п., то тут проверяем соответствия поведения реальной функции/объекта с поведением описанным в спецификации, правда используя теже методики:)(всмысле проверка на равенство и т.д.)
      • +2
        Вот я не понимаю, почему Вы различаете тесты и спецификации? Спецификация — это такой же тест, только описываемый декларативно (на языке бизнеса чаще всего).

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

        Более того, TDD не обязательно следует применять для проверки равенства. Применяйте методику для тестирования логики по спецификациям. То есть будет у вас тест, который делает ровно тоже самое, что сейчас Вы называете BDD.

        Единественное отличие, которое я вижу, то в BDD просто используется более высокоуровневое тестирование, обычно именуемое как автоматическое функциональное тестирование.

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

        Настоящее преимущество методика получает при использовании ее пользователем (заказчиком), который точно знает как ему нужно, а не как написано в спецификации.

        • –1
          «Более того, TDD не обязательно следует применять для проверки равенства. Применяйте методику для тестирования логики по спецификациям.»

          Вот так и появилось BDD:) И это уже более высокий уровень чем assertEquals и т.п.

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

          А то что касаеться FIT, вы просто путаете функциональные тесты с BDD. Функциональное тестирование, которое может проводить не только программист но и тестер и пользователь, прежде всего подразумавает «черный ящик», т.е. тестирующий не имеет представления как это работает, и тестирует некие функиональные куски и их интеграцию. В BDD пользователь конечно может прогнать тесты, но вот написать — врядли.
          • +2
            «Вот так и появилось BDD:) И это уже более высокий уровень чем assertEquals и т.п.»

            Я кажется понял откуда ноги растут: вы действительно сравниваете unit-тестирование с TDD?

            В моем понимании TDD — это совсем не фреймворк для unit-тестов, это методика разработки, использовать там assertEquals или test.CheckLogicIsCorrectlyImplemented() не регламентируется TDD, это всего лишь подход к разработке: сначала тест, потом логика.

            «А то что касаеться FIT, вы просто путаете функциональные тесты с BDD»

            Мне почему-то все же верится, что я ничего не путаю, поскольку BDD и есть фукнциональное тестирование, опять же основываясь на Вашей статье: «Мышление в терминах функциональности (того, что код должен делать)...»

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

            1. unit-тестирование, когда разработчик проверяет и фиксирует спецификацию к некоторому алгоритму, участку кода

            2. функциональное тестирование, когда QA или заказчик смотрит на систему не со стороны кода и конкретных алгоритмов, а с наружи, со стороны пользователя и пытается получить от системы ответы согласно функциональности системы.

            Хочу заметить, что TDD как таковое может покрывать как первый, так и второй тип тестирования, то есть написание тестов может начаться:

            а) до разработки логики (пишется программистом)
            б) до поставки продукта QA или заказчику на приемку (пишется QA или закачзиком)

            В этом смысле Ваше BDD есть некоторое расширение классического unit-тестирования и не более того.

            • –1
              Статья не моя:) Лично я считаю что функциональное тестирование и BDD — это разные вещи.
              Еще нужно добавить, для точности, что вообще то юнит тестирование не тоже самое что и TDD.
              Юнит тестирование — это метод тестирования, а TDD — это подход к разработке кода, основанный на юнит тестировании.
              Точно также и с BDD. В общем случае можно сказать что тестирование поведения это и есть функциональное тестирование, когда тестируется черный ящик. Но BDD — это подход к написанию кода, используя тесты поведения по спецификации. Что нам и демонстрирует фреймворк RSpec.
              • 0
                В целом со всем согласен, за исключением: «TDD — это подход к разработке кода, основанный на юнит тестировании»

                TDD — это подход к разработке кода, основанный на *модульном* тестировании, то есть на создании тестов невидимых и недоступных QA или закачзику.

                Из описания BDD в википедии прямым текстом следует (encourages collaboration between developers, QA and non-technical or business participants), что эта методика нацелена на разработку единых тестов для всех ролей, именно функциональных, описанных на языке бизнеса.

                И противопоставление к TDD основывается как раз на том, что в TDD готовятся тесты понятные только разработчикам, а в BDD тесты понятные всем.

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

                Посмотрите на FITPro, например, это в чистом виде BDD методика: тесты пишутся на бизнес-языке, они являются требованиями для разработки, несколько тестов можно объединить и получится спецификация на бизнес-правило.

                • –1
                  Понял причину нашего спора:) Вы смотрите на TDD/BDD более широко, с точки зрения общего цикла разработки ПО. Эта же статья и сам фреймворк RSpec нацелены на BDD только в контексте написания кода.
                  Для менеджера BDD одно, для программиста другое:)
                  • +1
                    Да, да, классический случай:

                    — мы используем Agile!
                    — а кто у вас Product Owner?
                    — ну как же, менеджер проекта…

                    Если уж и писать о методике и бросаться фразами «чем отличается от TDD», то нужно суть излагать.
    • 0
      По-моему, вы путаете функциональное/приемочное тестирование как этап проекта и разработку через тестирование (TDD/BDD). Пользователи (или QA-people) не пишут тесты к блокам кода.
      • 0
        Вот и я не пойму, человек говорит, что TDD не всегда хорошо, а хорошо BDD, которое суть больше подходит к функциональному тестированию.

        Причем функциональные тесты пишут разработчики, хотя всем известно, что такие вещи должны делать QA или заказчик (в случае Agile).
        • 0
          Можете указать, где именно спорный момент у автора?

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

          Т.е., по сути, отличие BDD от TDD только в том, в каком виде разработчику удобно писать тесты для кода (но не функциональные/UX-тесты).
          • 0
            «Можете указать, где именно спорный момент у автора?»

            Да, конечно, "… Behavior Driven Development и чем данная техника отличается от Test-Driven Development"

            Согласен с тем, что BDD полностью укладывается в методику TDD, и поэтому ничем не может отличаться. Комментом выше уже предположил, что автор путает unit-тесты (assertTrue, assertFalse) и TDD.

            Если отталкиваться от сравнения Unit-тестов и BDD, то конечно BDD иногда может быть удобнее, но поскольку BDD закрывает пробел между unit-тестами и функциональными тестами, то в этом и проявляется сложность вопроса: относится ли BDD к функциональным тестам или нет, поскольку можно написать такой тест, что он будет полностью повторять функциональный, поскольку оперируем бизнес-понятиями (то чем оперирует QA и закачзик), а не объектами и элементами архитектуры (то чем оперирует разработчик)

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

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

              Еще один нюанс в том, что _технически_ часть функциональных тестов можно сделать в виде юнит-тестов (точто так же, как, например, интеграционные тесты). Просто для кого-то это может быть запутано, но BDD не привносит ничего нового в тестирование как таковое (все и так тестируется полностью и BDD, скажем, не увеличит процент покрытия кода тестами), а только дает новый метод делать то, что мы уже делали с помощью TDD. Можно называть это не юнит-тестами, а «спецификациями» — суть проверки конкретного бизнес-правила от этого не поменяется.
              • 0
                Как раз привносит: вопрос заключается в требованиях к тестам.

                По TDD мы должны иметь

                а) спецификацию на модуль
                б) потом делать модуль и разрабатывать модульные тесты вперед
                в) потом проводить функциональное тестирование QA
                г) потом проводить приемочное тестирование заказчиком

                все эти активности базируются на исходных требованиях

                По BDD мы должны иметь:

                а) спецификации в смысле BDD, не требования, а именно бизнес-описание поведения системы

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

                Разница очевидна.

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

                Пока остаюсь при своем мнении:

                TDD и BDD можно и даже наверно нужно использовать совместно, поскольку TDD ориентирована на техническую составляющую: проверить DAO, проверить согласованность всяких XML-файлов, проверить работоспособность сугубо технических аспектов (логирование, как пример). BDD ориентировано на бизне-логику. Как известно, помимо бизнеса в реальном приложении масса сугубо технических задач, которые бизнесу вообще до фонаря.
                • 0
                  Так, я подумаю, как донести свою точку зрения :)

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

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

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

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

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

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

                    «Тестировщики и заказчики — это уже последующий этап (если там и используется некая схожая методика — то она все равно служит другим целям, нежели BDD как метод разработки).»

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

                      Я покопаю тему концепции BDD еще.

                      Отдельно об этом:

                      • 0
                        Отдельно об этом:

                        «Согласен, что любую системную фичу можно вытащить на уровень бизнеса, не знаю будут ли они только вам благодарны, скорее скажут: это ваше дело какие там библиотеки используются.»

                        Речь вовсе не идет о том, что что-то куда-то вытаскивать. Не нужно системные фичи на уровень бизнеса вытаскивать. Я говорю о том, что системные фичи точно так же, как и бизнес-логику, можно тестировать с помощью этих спецификаций.
                        • 0
                          Об этом и речь: если есть спецификация для тестирования системной фичи, то по определению, она должна быть написана на языке бизнеса, и скорее всего писать ее заказчику или аналитику.
                          • 0
                            Здесь встает вопрос ролей. Назовем этих людей не заказчиком и аналитиком, а более общим термином — «owner». В случае системных фич, owner'ом может быть архитектор, технический руководитель и разработчики.
                            • 0
                              И сразу же встает другой: зачем архитектору описывать спецификацию на понятном бизнесу языке? Ему куда как проще в терминах тестов все описать.
  • 0
    Странный вы пример привели. «TestNG, Selenium и FEST»… Selenium — это вообще функциональное тестирование, что не есть основа TDD. Я бы привел в пример множество фреймфорков семейства xUnit для самых разных языков и технологий.
  • 0
    TDD — это одна из крайностей разработки. В это схеме работы очень сложно создать нормальную архитектуру приложения. Ибо если использовать другую крайность — «нормальную разработку», то сначала надо создать спецификацию (srs), потом разработать архитектуру, потом высокоуровневое проектирование и тд…

    Я бы лично комбинировал данные подходы к разработке ПО
  • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    BDD1
    BDD2
    А у вас реально можно было ограничится ссылками ;)
  • 0
    И да, я, собственно, перенес бы подобную статью в блог TDD. Тестирование — это немного другое и несколько сбивает с толку.
    • 0
      Я бы вообще в Ruby перенес, про RSpec ведь. Жалко в два блога поместить нельзя.
  • 0
    Темы TDD, BDD и все, что за ними тянется(unit test, CI, refactoring и так далее) настолько обширны, что пытаться их изложить в какой-то одной статье неблагодарное дело. Когда пытаешься все это прицепить к какой-либо технологии, то получается вообще каша, а сама статья вызовет еще кучу вопросов и нареканий(сам с этим столкнулся).
    Поэтому я бы писал статью только про саму технологию и как именно она позволяет использовать те или иные приемы TDD(Предполагая, что аудитория знакома с техникой tdd). При это стараясь не вдаваться в теорию.
    Или же написать цикл статьей посвященных исключительно TDD и BDD, максимум давай небольшие примеры.
  • 0
    phpшникам стоит глянуть на пример www.phpspec.org/ сразу станет ясна суть
    • 0
      по мне так там сильно все притянуто за уши. Отличие лишь в том, что framework позволяет название метода разложить в текст (расставить пробелы).

      а спецификация все равно пишется на PHP, тестировщикам и заказчикам это явно не понравится. А как разработчику мне все равно как будет текст написан camel-ом или через пробел.
      • 0
        ну в php есть вообще проблема с нормальным TDD фреймворком, для phpunit нет человекопонятного гуя + вагон пир зависимостей, simpletest очень медленно развивается в trunk, тк нет интереса разработчиков, а интересующихся сразу же переманивают к phpunit показывая икону zend framework

        phpspec я привел лишь как пример уже работающего фреймворка для тех кто хочет пробнуть свои силы.
  • 0

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.