Pull to refresh

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

Reading time 4 min
Views 2.4K
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/
Tags:
Hubs:
+12
Comments 6
Comments Comments 6

Articles