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

    specter-log Specter – инфраструктура для составления объектно-поведенческих спецификаций для .NET. Он предоставляет возможности для обеспечения разработки, руководствуясь поведением системы (BDD), требуя от разработчиков написания исполняемой спецификации для объектов перед написанием самих объектов. Технически это ни чем не отличается от разработки по средствам тестирования (TDD), хотя различия в форме написания снимают психологический барьер для написания “тестов” для кода, которого ещё не существует. Есть множество проектов для различных платформ, реализующих данную идею (К примеру RSpec для Ruby, NSpec для .NET. Подробнее о средах здесь).
    Specter использует возможности мета-программирования языка Boo (CLR .NET) для написания неплохо читаемых спецификаций.

    Пример практики BDD при работе со Specter


    Для нашего примера рассмотрим спецификацию мини-бара Бендера, она будет выглядеть следующим образом:
    import Specter.Framework
    import Bender
    context "At Bender's bar":
       _bar as duck #our subject is defined in the setup block below
       setup:
         subject _bar = Bender.MiniBar()
       #one-liner shorthand
       specify { _bar.DrinkOneBeer() }.Must.Not.Throw()
       specify "If I drink 5 beers then I owe 5 bucks":
         for i in range(5):
           _bar.DrinkOneBeer()
         _bar.Balance.Must.Equal(-5)
       specify "If I drink more than ten beers then I get drunk":
         for i in range(10):
           _bar.DrinkOneBeer()
         { _bar.DrinkOneBeer() }.Must.Throw()
    * This source code was highlighted with Source Code Highlighter.


    Хотелось бы отдельно отметить возможность читаемости данного кода сторонними от программирования людьми.
    Что же мы такое написали?
    Всё очень просто, мы создали привычный нам по NUnit TextFixture Class и описали Test методы. Сейчас расскажу, как это получилось. Обратим внимание на следующую строчку:
    context "At Bender's bar":
    * This source code was highlighted with Source Code Highlighter.


    Мы определяем некий context. Что же он из себя представляет? На самом деле, если полезть рефлектором в сборку, которую мы полчим, при компиляции спецификации, мы обнаружим, что данный контекст являет собой сам TextFixture Class:
    [NUnit.Framework.TestFixture]
    class EmptyStack:
    * This source code was highlighted with Source Code Highlighter.


    Далее посмотрим на:
    setup:
      subject _bar = Bender.MiniBar()
    * This source code was highlighted with Source Code Highlighter.


    Это являет собой привычный нам SetUp:
    [NUnit.Framework.SetUp]
     public void SetUp()
    {
       subject _bar = Bender.MiniBar();
    }
    * This source code was highlighted with Source Code Highlighter.


    Следующая интересная нам строчка:
    specify { _bar.DrinkOneBeer() }.Must.Not.Throw()
    * This source code was highlighted with Source Code Highlighter.


    Является уже тестом:
    [NUnit.Framework.Test]
    public void BarDrinkOneBeerMustNotThrow()
    {  
        Assert.DoesNotThrow(_bar.DrinkOneBeer());
    }
    * This source code was highlighted with Source Code Highlighter.


    Аналогично следующие строки соответствуют:
    specify "If I drink 5 beers then I owe 5 bucks":  
    for i in range(5):
         _bar.DrinkOneBeer()
       _bar.Balance.Must.Equal(-5)
    * This source code was highlighted with Source Code Highlighter.


    Так же тестам:
    [NUnit.Framework.Test]
    public void IfIDrink5BeersThenIOwe5Bucks()
    {
       for (int i = 0; i == 5; i++)
         _bar.DrinkOneBeer();
       Int32MustModule.Must(_bar.Balance, “Bar balance must equal -5").Equal(-5);
    }
    * This source code was highlighted with Source Code Highlighter.


    И ещё одна строчка:
    specify "If I drink more than ten beers then I get drunk":
       for i in range(10):
         _bar.DrinkOneBeer()
       { _bar.DrinkOneBeer() }.Must.Throw()
    * This source code was highlighted with Source Code Highlighter.


    Соответствует:
    [NUnit.Framework.Test]
     public void IfiDrinkMoreThanTenBeersThenIGetDrunk()
    {
       for (int i = 0; i == 10; i++)
       {
         _bar.DrinkOneBeer();
       }
       Assert.Throws((typeof(InvalidOperationException), _bar.DrinkOneBeer()); }
    * This source code was highlighted with Source Code Highlighter.



    Со спецификацией покончено, далее нам необходимо написать КОД нашего приложения, т.к. specter выругался о том, что спецификация не реализована:
    minibar-result1[1]
    Что же, реализуем наш мини-бар:
    namespace Bender     
    class MiniBar:
       pass
    * This source code was highlighted with Source Code Highlighter.


    И добавляем необходимый метод:
    namespace Bender
    class MiniBar:
        def DrinkOneBeer():
            pass
        [getter(Balance)]
        _balance = 0
    * This source code was highlighted with Source Code Highlighter.


    Но тем не менее specter не доволен, так как наш метод не реализует работу с балансом, описанную в спецификации:
    minibar-result2[1]
    Придётся добавить реализацию:
    namespace Bender
    class MiniBar:
        def DrinkOneBeer():
            _balance--
    	if _balance < -10:
    		raise System.Exception("i'm drunk")

        [getter(Balance)]
        _balance = 0
    * This source code was highlighted with Source Code Highlighter.


    По-моему specter’у всё понравилось:
    minibar-result3[1]
    Отлично, вот мы и попрактиковали BDD.
    Итак, мы получаем привычные нам unit-тесты, однако мотивация написания немного измениться, а так же нами будет приобретена такая возможность, как предоставление specter-спецификации тестов в качестве документации. Мне кажется это отличная идея!

    Ресурсы


    О BDD почитать можно здесь:
    http://habrahabr.ru/blogs/testing/52929/ (про BDD на русском на примере RSpec, в конце статьи есть ссылки)
    Знакомство с Behavior Driven Development (BDD) (рус.)
    Скачать Specter можно здесь:
    http://specter.sourceforge.net/
    • +12
    • 1,9k
    • 6
    Поделиться публикацией

    Похожие публикации

    Комментарии 6

      +1
      Хороший обзор.
      Эх, сам все хочу переместиться от TDD к BDD при организации команды. Это очень круто и удобно, а главное «человеко-понятно».

      Тесты уже и так давно пишутся не в в стиле:
      «Test_Orders_SetState_Created_By_MethodCall»

      а как
      «Order_State_Should_Become_Created_When_SetStateMethod_Called», «Order_Object_Should_Be_Created», и т. д., где ключевое слово «Should» присутствует во всех без исключения тестах, что так и клонит в сторону BDD и описания правил «человеческим языком» и когда тестируется не метод класса или класс, а поведение, которое независимо от методов должно быть реализовано.
        +1
        Спасибо! Specter пока (хотя скорее уже) не набрал должной популярности, поэтому думаю, что можно обратить внимание на RSpec или Cucumber, благо .NET позволяет.
          0
          Это да. Я уже интересовался темой, хотя, признаюсь, не очень глубоко. Но вы же понимаете — одно дело понять одному, другое дело внедрить все это в продакшн-поток ;)
            0
            С появлением инструментов, внедрить в продакшен станет проще :)
        0
        Возможно язык Boo и прост но думаю это не очень удобно писать тесты на нем.

        Предлагаю сравнить с моей небольшой разработкой — code.google.com/p/artspec/
          0
          Лично мне непонятно чем это лучше чем использование связки MbUnit+NBehave, например. Мне кажется что если и использовать языки вроде Воо, то из них нужно выжимать как раз то, что C# дать не может.

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

          Самое читаемое