BDD и расположение «когда» - PullRequest
5 голосов
/ 19 ноября 2009

Я видел, как мне кажется, два подхода к BDD. Разница зависит от местоположения «когда»:

В подходе 1 время является частью спецификации:

AnEmptyStack.isNoLongerEmptyAfterPush

В чистой терминологии "дано тогда" это:

"Если задан пустой стек, когда он помещается, он больше не пуст."

Таким образом, «когда» является частью метода спецификации:

isNoLongerEmptyAfterPush(){
     stack.push(anObject);
     Assert.notEmpty(stack);
}

В подходе 2 время определяется на уровне класса. То есть когда обычно вызывается в настройке.

class WhenAnEmptyStackIsPushed(){

   setup(){
      stack.push();
   }

   public void thenItIsNotEmpty(){
      assert(stack.notEmpty())
   }
}

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

Однако для простоты тестирования я склоняюсь к первому способу. Большая часть боли, которую я испытываю при тестировании, связана с настройкой. То есть я должен получить СУТ в определенном состоянии. Находясь в этом состоянии, обычно это всего одна строка кода, которая фактически вызывает какое-то поведение в нем. Таким образом, наличие нескольких вариантов поведения для каждого класса (т. Е. Для контекста настройки) использует однократную настройку класса.

Итак, я ищу мысли. Один подход предпочтительнее другого?

Ответы [ 2 ]

2 голосов
/ 29 января 2010

В зависимости от вашей среды тестирования, вы можете иметь лучшее из обоих миров.

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

public class SomethingDoerSpecs
{

    public abstract class concern : observations_for_a_sut_with_a_contract<IDoSomething,SomethingDoer>
    {
        // here I can define setup that will be common to all subsequent tests
        context c = () => ...
    }

    public class When_asked_to_do_something : concern
    {
        context c = () =>
        {
            // setup specific to this context goes here
        };

        because b = () => sut.DoSomething();

        it should_open_a_database_connection =
             () => mock_db_connection.was_told_to(x => x.Open());

        it should_set_the_result_value_to_true =
             () => sut.Result.should_be_true();

        // etc.
    }

   public class When_asked_to_do_something_but_the_database_is_unavailable
        : When_asked_to_do_something
    {
       context c = () =>
         {
            // additional context
         };

         because b = doing(() => sut.DoSomething());

         it should_throw_a_custom_exception = () =>
         {
            exception_thrown_by_the_sut.should_not_be_null();
             exception_thrown_by_the_sut
                 .should_be_an_instance_of<CouldNotDoSomethingException>();
         };

    }
}

Это просто для иллюстрации того, что тестовые классы часто могут быть вложенными, поэтому вы все равно можете выполнить «большое» Когда… и повторно использовать состояние, которое вы установили ранее, наследуя, когда вам нужна большая специфичность контекста. Конечно, вы должны быть уверены, что ваш фреймворк будет сбрасывать настройки между наборами утверждений.

Кстати, весь синтаксис делегата, который я здесь показываю, взят из библиотеки DevelopWithPassion.Bdd Жана-Пола Будху, которую вы можете найти на Github.

0 голосов
/ 12 декабря 2009

Я думаю, что ваш вариант 2 предпочтительнее. По моему мнению, каждый тестовый класс должен устанавливать SUT в одном состоянии, и каждый тестовый метод является тогда наблюдением за этим объектом. Я думаю, что это имеет больше смысла, если вы добавите в класс еще несколько наблюдений. Если каждое наблюдение является только наблюдением и наблюдением без дополнительных действий, я думаю, вы увидите, как все наблюдения естественным образом связаны друг с другом.

Если вы выберете альтернативу 1, вы не группируете наблюдения, потому что они наблюдают разные аспекты одного и того же объекта (состояния), а скорее потому, что они имеют какое-то общее начальное состояние, которое вы хотите использовать повторно. Не группируйте тесты для повторного использования кода, группируйте тесты, потому что они принадлежат друг другу, и используйте другие средства для повторного использования кода в качестве вспомогательных классов / методов или даже наследования (то есть все классы, связанные со стеком, могут наследовать от класса, который создает пустой стек).

...