Полноценный Behavior Driven Development на .NET

Введение

Меня переполняют эмоции. Это связанно с тем, что сегодня мой товарищ сообщил о следующем: есть полноценная реализация BDD движка для .NET.

Называется проект SpecFlow

Исходный код ныне базируется на GitHub http://github.com/techtalk/SpecFlow

Немного теории

Вопрос о том, что же такое Behavior Driven Development неоднозначен. Особенно противоречивы его отношения с Test Driven Development. Однако предлагаю абстрагироваться от сравнения двух подходов и договорится, что суть этого есть одно и тоже, т.к. особенных различий нет. Основной чертой выделяющей BDD от TDD по-моему усмотрению является ориентация на различных участников процесса производства программного обеспечения, будь то менеджер, разработчик, тестер, заказчик. Вы вместе пишете тесты, и вместе можете развивать проект в нужном направлении и вполне понятных терминах. Так же на ум в данном случае приходит такой термин, как Ubiquitous Language из Domain Driven Design. А ведь и в самом деле, обобщение терминологии описания вашей предметной области вполне может ложится на практику BDD.

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

Все известные инструменты (к примеру mock-объекты) и практики (такие как шаблоны тестирования), используемые вами при разработке в стиле TDD, изменятся незначительно при переходе на BDD стиль, в связи с чем можно начать применять BDD уже сегодня. Так же вполне возможно, что вам необходимо будет произвести более низкоуровневое тестирование некоторых компонентов, что несомненно приведёт к смешиванию двух практик, что несомненно должно происходить осознано и с должен вниманием.

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

Инструменты

cuke_logo[1] На данный момент существует множество BDD framework’ов, однако “наиболее верным” с точки зрения родоначальников данного термина (Dan North, David Chelimsky, Aslak Hellesøy) является такой проект как: Cucumber (ранее известный как RSpec). Этот проект написан для Ruby. Большинство RubyOnRails разработчиков (как основные представители ruby сообщества) пользуются именно этим фреймворком для тестирования своих продуктов.

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

Инструменты для .NET в основном копируют формат и принципы, заложенные в Cucumber, их существует несколько:

  • SpecFlow – предмет сегодняшнего разговора (из плюсов – аналогия с Cucumber и интеграция в Visual Studio 2008 и 2010)
  • NBehave – работа с данным фреймворком близка к возможностям Cucumber в настоящее время (до этого он так же был в разряде “многословных” и  это, наверное, наиболее распространенный BDD фреймворк среди .NET разработчиков)
  • NSpecify, NSpec – довольно-таки “многословные” фреймворки для BDD
  • Specter – аналогично, примечателен тем, что написан на Boo

Теперь практика

Для того, чтобы проиграться с этим замечательным инструментом нам понадобятся:

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

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

Создадим новый solution в студии, в котором будет два проекта.

  1. Calculator
  2. Calculator.Tests

Для того, чтобы его описать в решении + подготовить реализацию нам понадобится немного инфраструктурных заморочек в проекте Calculator.Tests (ниже работа пока будет только с ним):

  • добавить reference на TechTalk.SpecFlow.dll (которая скорее всего окажется в папке с установленным SpecFlow)
  • добавить reference на nunit.framework.dll (его следует искать в GAC’е)

Теперь всё готово для того, чтобы погрузиться в мир BDD. Откиньтесь на спинки кресел и расстегните ремни.

В пункте Add New Item у вас наверняка появились новые пункты, начинающиеся на SpecFlowimage

Далее задайте имя нашей фиче addition.feature и щелкните Add.

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

http://github.com/aslakhellesoy/cucumber/blob/master/examples/i18n/en/features/addition.feature# language: en

Feature: Addition

  In order to avoid silly mistakes

  As a math idiot 

  I want to be told the sum of two numbers

  Scenario Outline: Add two numbers

    Given I have entered <input_1> into the calculator

    And I have entered <input_2> into the calculator

    When I press <button>

    Then the result should be <output> on the screen

  Examples:

    | input_1 | input_2 | button | output |

    | 20      | 30      | add    | 50     |

    | 2       | 5       | add    | 7      |

    | 0       | 40      | add    | 40     |

Жмём ctrl+s и большая часть работы по описанию выполнена.

Теперь нам надо сделать описание steps, для того, чтобы связать сценарий с тестируемым модулем. Для этого добавьте новый элемент SpecFlow Step Definition, назовём мы его calculator_steps.cs.

Данный файл уже содержит в себе некоторые записи по-умолчанию, в принципе они вполне подходят для нашего примера. (Кстати, обратите внимание как строго упорядочены все методы по шаблону AAA: Arrange, Act, Assert, в данной интерпретации Given, When, Then)

Однако немного их всё же придется изменить.

Внутри данного метода:

[Given("I have entered (.*) into the calculator")]
public void GivenIHaveEnteredSomethingIntoTheCalculator(int number)
{
  //TODO: implement arrange (recondition) logic
  // For storing and retrieving scenario-specific data,
  // the instance fields of the class or the
  //     ScenarioContext.Current
  // collection can be used.
  // To use the multiline text or the table argument of the scenario,
  // additional string/Table parameters can be defined on the step definition
  // method.

  ScenarioContext.Current.Pending();
}

Мы видим, что на нашем калькуляторе печатают некоторые числа, ок, подтвердим это:

[Given("I have entered (.*) into the calculator")]
public void GivenIHaveEnteredSomethingIntoTheCalculator(int number)
{
    Calculator.Input(number);

}

И не стесняйтесь того, что пока Visual Studio не совсем понимает, что такое Calculator 😉

Отлично, смотрим на следующий метод, его мы тоже немного поправим:

[When("I press add")]
public void WhenIPressAdd()
{

    Calculator.Add();

}

И, вслед за ним, удостоверимся, что получаем именно то, чего желаем:

[Then("the result should be (.*) on the screen")]
public void ThenTheResultShouldBe(int result)
{
    Assert.IsEqual(result, Calculator.Result);
}

Теперь самое время разобраться с нашим калькулятором.

Создадим следующее поле:

protected Calculator Calculator = new Calculator();

Теперь пора бы разобраться и с типом Calculator, его мы создадим в проекте Calculator. Так же добавим reference из проекта Calculator.Tests на проект Calculator. С типом так же всё в порядке, однако не хватает некоторых методов, их тоже следует создать.

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

image

Теперь можно заняться имплементацией нашего калькулятора. На данный момент я предлагаю следующее:

public class Calculator
{
  private IList<int> _buffer = new List<int>();

  public int Result { get; private set; }

  public void Input(int number)
  {
    _buffer.Add(number);
  }

  public void Add()
  {
    Result = _buffer.Sum();
    _buffer.Clear();
  }

}

Запускаем наши тесты и видим, что они проходят, супер!

image

Вроде бы с первой фичей мы успешно разобрались, почему бы не взяться за вторую?

Второй возможностью будет деление:

http://github.com/aslakhellesoy/cucumber/blob/master/examples/i18n/en/features/division.feature

# language: en

Feature: Division

  In order to avoid silly mistakes

  Cashiers must be able to calculate a fraction

  Scenario: Regular numbers

    Given I have entered 3 into the calculator

    And I have entered 2 into the calculator

    When I press divide

    Then the result should be 1.5 on the screen

*в оригинале для cucumber у aslakhellesoy несколько иной синтаксис, но он на данный момент не поддерживается в SpecFlow

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

[When("I press divide")]
public void WhenIPressDevide()
{
  Calculator.Devide();
}

Добавить так же метод в класс Calculator.

Скомпилировать наш проект. И запустить тесты. Конечно же они не пройдут, т.к. деление мы с вами ещё не реализовали:

public void Devide()
{
  Result = _buffer[0] / _buffer[1];
  _buffer.Clear();
}

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

Материалы

Исходный код рассматриваемого проекта http://dl.dropbox.com/u/4070949/Calculator.zip

Скринкаст от создателей SpecFlow http://www.specflow.org/specflow/screencast.aspx

Моя статья Пример практики BDD при работе со Specter Framework

Книга The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends

Википедия http://en.wikipedia.org/wiki/Behavior_Driven_Development