Тестирование с использованием BDD

    Введение


    Современные проекты все чаще предъявляют высокие требования к покрытию автоматическими тестами. В наше время писать тесты не просто признак хорошего тона, но одно из требований, которое предъявляется к коду. Все чаще мы слышим такие аббревиатуры, как TDD (Test Driven Development) и BDD (Behaviour Driven Development) и многие строго следуют этим подходам в разработке.
    BDD это одна из разновидностей TDD, и об этом я хотел бы написать в этой статье. Точнее не о самом BDD, а о frameworks, которые нам предоставляет индустрия на сегодняшний день. А если уж быть совсем точным, то о трех из них: spock, easyb и cucumber.

    TDD и BDD

    Я не буду тут ссылаться на статьи и презентации корифеев IT индустрии. Мне запомнилась одна фраза из Twitter по поводу TDD которая засела в моем сознании, и которая на мой взгляд четко и коротко характеризует TDD подход. К сожалению дословно я её привести не могу, но смысл в ней следующий: «если вы следуете TDD, то можете быть 100% уверены, что каждая строчка кода была написана благодаря упавшему(ым) тесту(ам)». Я видел и слышал много дебатов по поводу достоинств и недостатков TDD и BDD, но а) тесты писать надо б) если код был написан благодаря упавшему тесту, то этому коду можно доверять, и с легкостью его изменять (рефакторить) не боясь испортить поведение системы.
    Теперь про BDD. Появилось это явление позже и как утверждает Фаулер в статье «Mocks Aren't Stubs» благодаря так называемым мокистам. С другой стороны этот подход активно продвигают ребята из Agaile тусовки, сводя к минимуму расстояние между разработчиками, пользователями и аналитиками систем. Достигается это путем получения Executable Scenarios, иными словами, сценарии которые описывают пользователи переводятся в исполняемый тест. BDD frameworks с этим удачно справляются.
    Теперь перейдем к сравнению.
    Все примеры описывают один и тот же сценарий. Я опущу описание проблемы решение, которой необходимо покрыть тестами, потому как сами сценарии должны ясно описать её.
    Автор статьи приводит реализации BDD в порядке возрастания симпатии к ним.

    Easyb

    Данный framework написан на Groovy. Как и все BDD реализации поддерживает нотацию Given-When-Then. Легко интегрируется в Continuous Integration (CI).
    Вот пример сценария:
    description "This story is about sqrt optimisation algorithm"
    narrative "this shows sqrt optimisation", {
    as a "java developer"
    i want "to know how sqrt optimisation works"
    so that "that I can pass google interview"
    }

    before "init input and expected result",{
    }

    where "complete scenarios data",{
    input = [[5, 10, -3, 17, 12, 1, -2, 13, -12], [5, 8, 13, 5, 21, 6, 3, 7, -2, 4, 8, 12]]
    leftIndex = [2,3]
    rightIndex = [5,10]
    expectedSumm = [27,51]
    }

    scenario "find summ within two indexes #leftIndex and #rightIndex of the array #input",{
    given "An Sqrt algorithm implementation",{
    alg = new SqrtDecompositionSum(input.toArray(new int[0]))
    }

    when "calc sum between two indexes", {
    actualSum = alg.calcSummBetween(leftIndex, rightIndex)
    }

    then "summ should be equal expected #expectedSumm", {
    actualSum.shouldBe expectedSumm
    }
    }

    Вот как выглядит результат теста:
    image

    Тут «вылазит» первый недостаток easyb. Дело в том, что непонятно откуда взялось 2 сценария, в то время как описан 1. Если вглядеться в секцию where сценария, то можно увидеть, что подготавливается 2 набора входных и ожидаемых значений. К сожалению конструкция where не документирована даже на сайте проекта, по крайней мере я её там не нашел.
    Ниже приведен пример упавшего теста-сценария


    Как видим результат вполне читаем. Обратите внимание на строку actualSum.shouldBe expectedSumm. Это sugar, который предоставляет easyb для проверки ожидаемого с актуальным результатом.
    Для того чтобы запустить сценарий из IDE необходимо поставить easyb plugin.
    Вторым недостатком я могу отметить то, что последний раз обновления easyb было в 2010 году, что на мой взгляд уже достаточно давно.
    За подробностями обращайтесь на сайт проекта.

    Spock

    Spock, как и EasyB выходец из groovy. Его очень любят использовать разработчики Groovy/Grails. Наш сценарий будет выглядеть так:
    class SqrtSumAlgSpecTest extends Specification {
    Algorithm alg
    def "Sqrt sums scenarios"(){
    when:
    alg = new SqrtDecompositionSum(input.toArray(new int[0]))
    then:
    outputSumm == alg.calcSummBetween(leftIndex, rightIndex)
    where:
    input | leftIndex | rightIndex | outputSumm
    [5, 10, -3, 17, 12, 1, -2, 13, -12] |2 |5 |27
    [5, 8, 13, 5, 21, 6, 3, 7, -2, 4, 8, 12] |3 |10 |52
    }
    }


    Spock мне больше нравится конструкцией where. Для того, чтобы создать spock спецификацию, необходимо создать groovy класс унаследованный от spock.lang.Specification.
    Ниже приведен пример упавшего теста-сценария:


    Spock, на мой взгляд, ближе к разработчику нежели к аналитику или QA инженеру, однако все равно легко читаем.

    Cucumber

    С Cucumber я познакомился совсем недавно, и чем больше я с ним экспериментировал, тем больше он мне нравился. В отличие от первых двух, Cucumber выходец из Ruby. Существует его реализация для Java и С#.
    Сценарии на cucumber состоят из двух файлов: собственно сценарий, и его реализация на Java, C#, Ruby. Это позволяет отделить сценарий от реализации, что делает сценарии абсолютно обычным повествованием на английском языке, приведем пример

    Feature: Sqrt Sums Algorithm Feature
    In order to ensure that my algorithm works
    As a Developer
    I want to run a quick Cuke4Duke test

    Scenario Outline: Sqrt Sums Alg Scenario
    Given The input array <input array>
    When The calc sum between <Left index>, <Right index>
    Then The summ is <output summ>.

    Examples:
    |input array |Left index |Right index|output summ|
    |5, 10, -3, 17, 12, 1, -2, 13, -12 |2 |5 |27 |
    |5, 8, 13, 5, 21, 6, 3, 7, -2, 4, 8, 12 |3 |10 |52


    Кстати, сценарии в cucumber называют features.
    А вот реализация

    public class SqrtsumsalgFeature {
    private Algorithm alg;
    private int result;

    @Given ("^The input array ([\\d\\s\\-\\,]*)$")
    public void theInputArray(String input) {
    String[] split = input.split(",");
    int[] arrayInput = new int[split.length];
    for (int i = 0; i < arrayInput.length; i++) {
    arrayInput[i] = Integer.valueOf(split[i].trim());
    }
    alg = new SqrtDecompositionSum(arrayInput);
    }

    @When ("^The calc sum between ([\\d]*), ([\\d]*)$")
    public void theCalcSumBetween(int L, int R) {
    result = alg.calcSummBetween(L, R);

    }

    @Then ("^The summ is ([\\d]*).$")
    public void theSummIs(int expectedResult) {
    Assert.assertThat(result, is(expectedResult));
    }
    }


    Тут нужно соблюдать Naming Conventions как в именах файлов сценария и реализации, так и в именах методов реализации и шагов сценария. Иными словами они должны соответствовать. Соответствие достигается путем использования аннотаций Given, @When, Then и строк регулярных выражений в качестве аргументов к аннотациям.
    Используя группы регулярных выражений можно выделять аргументы методов реализации.

    Ниже приведен пример прошедшего теста на cucumber



    А вот пример упавшего feature



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

    Итог


    Статья получилась выше среднего. Хотелось бы еще описать интеграцию с maven и Continuous Integration. Думаю, что это будет тема будущего поста.
    Пишите исполняемые сценарии, это не только полезно, но и доставляет удовольствие.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 18
    • +11
      Относительно TDD. Под любой TDD может быть написан полностью ему соответствующий, но не являющийся осмысленным код. Исключение — если тесты покрывают 100% множества входных данных, что, мягко говоря, не есть быть возможно в реальных ситуациях. Таким образом, достаточно сделать «обратную компиляцию» тестов, чтобы получить фигню, которая всем хороша, только не работает.
      • +8
        Под любой пост может быть написан полностью ему соответствующий, но не являющийся осмысленным комментарий.
        • +2
          Вы вообще не пишете тесты?
          • +1
            С учётом тех программ, которые я «пишу» — нет, тестов не пишу. Наши программисты — пишут.
            • 0
              Тогда я не пойму почему вы критикуете данный подход.
              А «подгоном» для галочки, можно много чего сделать.
              • 0
                Я его не критикую в смысле «отвергаю», я скорее ставлю под сомнение тезис agile о том, что можно творить что угодно в коде, пока он проходит тесты. До определённой степени это так, но принимать за максиму я бы не стал.
                • 0
                  Да, нельзя быть ни в чем быть на 100% уверенным. Все упирается в качество автоматизированных тестов. Заметьте не только степень покрытия, Unit тестами, кода, но и Acceptance тесты. Когда вы пишите Unit тест, вы подтверждаете правильность реализации с точки зрения разработчика, Acceptance тесты подтверждают то, что вы реализовали именно то что хотел бизнес. Иными словами Unit/Integration тесты для разработчиков, Aceeptance для всех остальных.
                  Поверьте, если у вас разумно написаны тесты всех уровней пирамиды тестирования, то творить с кодом все, что угодно они вам просто не позволят.
        • –2
          1) Сценарий — это сценрий, фича — это фича. Давайте не путать понятия
          2) «Тестирование с использованием BDD» равносильно фразе «Вождение машины с помощью дороги».
          • 0
            2) Все относительно, например: поезд является машиной, рельсы — дорогой. А съехать поезд с рельсов не может (граничные случаи не рассматриваем)
            • 0
              Я не к тому. Не BDD используется в тестах, а тесты в BDD.
          • 0
            Материал очень полезен, но мне не понравилось оформление:
            1) Примеры кода можно оформить в тегом «код», а то сейчас оно выглядит как будто автор применил тег «цитата»
            2) Поясняющий текст к картинке слишком далеко расположен от картинки, которую он поясняет.

            ЗЫ: Сорри за занудство! ;)
            • 0
              Со своей стороны прошу прощения за оформление. Да действительно под конец написания статьи, я заметил, последняя часть его отображается как код, такое впечатление, нету закрывающегося тэга . Искал-искал — не нашел.
              Тэгом «цитата» я вообще не пользовался. Однако я еще раз проверю текст.
              Второй пункт приму к сведению.
              Спасибо за критику.
              • 0
                Исправил проблему с тэгами, оказывается дело в < > символах в тексте cucumber feature
              • 0
                Мыша на скриншоте порадовала.
                • +1
                  Круто, не был в курсе кукумбера для Java и C#.
                  Для некомпилируемых фреймворков — t/bdd крайне необходимо, без этого вы получите свой маленький ад.
                  • 0
                    «некомпилируемых фреймворков», что вы имели в виду? Буду ли я прав если предположу, что вы говорите о динамических языках программирования?
                  • 0
                    Для тех, кому интересно покопаться в реализации самому, милости прошу на GitHub. Все три BDD frameworks представлены в этом проекте, собственно это полигон для этой статьи. Кроме того, можно поглядеть сюда, чтобы увидеть как работает Spock в связке с Selenium2/webdriver + Geb.
                    • 0
                      а кто в CnS копаться будет?
                      +1 за статью, пиши еще.

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