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

Шаблоны проектирования Cucumber BDD сценариев


Цели:

  • получить готовый инструмент, при помощи которого станет возможным стандартизировать процессы разработки и контроля качества исполняемых сценариев, построенных для работы в Cucumber-based технологических стеках (cucubmer jvm, SpecFlow и проч.)
  • получить набор правил, позволяющих специалистам с разных проектов легко мигрировать между проектами без длительной фазы привыкания
  • получить чистый, легко-читаемый код сценариев, который легко расширяется и слабо подвержен полным переписываниям текстов сценариев при минимальных изменениях UI

Итак, поехали!

Шаблон Sequence


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

Способ решения: Выстроить сценарий в соответствии с последовательностью: Дано — Действие — Результат.

Результат: Сценарий приобретает логическое разделение на зоны ответственности, становится более понятным стороннему наблюдателю.

Применение шаблона: Рассмотрим на примере:

Given Trader Application is started
And user clicks on File menu item
And user clicks on File/New menu item
Then New File dialog box should be shown

Содержит действия в Given секции. По рекомендации шаблона проектирования, действия должны находиться в секции When:

Given Trader Application is started
When user clicks on File menu item
And user clicks on File/New menu item
Then New File dialog box should be shown

Шаблон Background


Симптом: В каждом сценарии файла проводится одинаковая подготовительная работа по достижению некоторого состояния приложения.

Способ решения: Вынести общий код в секцию Background.

Результат: Каждый сценарий становится лаконичным и делает только те действия, которые написаны только для него. Вынося общий код, становится легкой его будущая поддержка.

Применение шаблона:

Scenario: user should see New File dialog when selects File/New menu item
  Given Trader Application is started
  When user clicks on File menu item
  And user clicks on File/New menu item
  Then New File dialog box should be shown

Scenario: user should see Open File dialog when selects File/Open menu item
  Given Trader Application is started
  When user clicks on File menu item
  And user clicks on File/Open menu item
  Then Open File dialog box should be shown

Содержит одинаковые предусловия в Given секции. По рекомендации шаблона проектирования, они должны находиться в Background секции.

Background:
 Given Trader Application is started
 
Scenario: user should see New File dialog when selects File/New menu item
  When user clicks on File menu item
  And user clicks on File/New menu item
  Then New File dialog box should be shown

Scenario: user should see Open File dialog when selects File/Open menu item
  When user clicks on File menu item
  And user clicks on File/Open menu item
  Then Open File dialog box should be shown

Шаблон Strategy


Симптом: Для одного и того же действия над различными сущностями одного типа определены различные определения шагов действий в их реализации.

Способ решения: Сделать действие общим, принимая сущность как опцию.

Результат: Код сценариев становится более предсказуем и ясным, код реализации действий сокращается до одного метода. Облегчается будущая поддержка кода.

Применение шаблона:

Scenario: user should see New File dialog when selects File/New menu item
  Given Trader Application is started
  When user clicks on File menu item
  And user clicks on File/New menu item
  Then New File dialog box should be shown

Groovy part:
When(~'user clicks on File menu item') { ->
  $('#FileMenuItem').click()
}

When(~'user clicks on File/New menu item') { ->
  wait {
    $('#FileNewMenuItem').displayed
  }
  $('#FileNewMenuItem').click()
}

Содержит одинаковый по смыслу код для двух шагов. По рекомендации шаблона проектирования, они должны быть объеденены в один метод.

When(~'user clicks on (.+)') { ControlDsl control ->
  dsl.displayed.shouldBecome(true)
  dsl.click()
}

Шаблон Interface


Симптом: Для различных типов сущностей определены различные определения шагов одних и тех же действий в их реализации

Способ решения: Выделить свойства и действия над сущностями в отдельные интерфейсы, группируя по общим характеристикам. Сами сущности в определениях шагов должны приниматься не через конечные типы, а через интерфейсы.

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

Применение шаблона:

Scenario outline: user should see New File dialog when selects File/New menu item
  Given Trader Application is started
  When user clicks on <ui element>
  And user clicks on File/New menu item
  Then <entity> should exist
  Examples:
  | ui element     | entity             |
  | File menu item | New File dialog    |
  | regedit icon   | HKLM/../newfilekey |


Groovy part:
Then(~'(.+) should exist)') { UIControlDsl dsl ->
  dsl.displayed.shouldBe(true)
}

Then(~'(.+) should exist)') { RegistryKey key ->
  key.shuoldNotBeNull()
}

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

Then(~'(.+) should exist') { ICanExist entity ->
  dsl.exist.shouldBecome(true)
}

реализация при этом выносится в реализации сущностей

Шаблон Inside


Симптом: Для сущностей, находящихся в различных частях их иерархии внутри приложения строится плоская иерархия регистраций сущностей в коде обслуживания cucumber bdd и как результат — пересечения имен (Trade Ok button, Save Ok button, Are You Sure Ok button вместо просто Ok button).

Способ решения: Реализовать иерархическую регистрацию сущностей с сохранением информации о вложенности одних сущностей в другие.

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

Применяется совместно с паттерном Scope.

Применение шаблона:

Scenario outline: user should see New File dialog when selects File/New menu item
  Given Trader Application is started
  When user clicks on File menu item
  And user clicks on File/New menu item
  Then user clicks on Ok button inside New File dialog box

Groovy part:
When(~'user clicks on (.+)') { ControlDsl control ->
  dsl.displayed.shouldBecome(true)
  dsl.click()
}

Registration part:

class NewFileDialogDsl : ControlDsl {
  controls : {
      "Ok button" : { $('#NewFileOkButton') }
  }
}

class OpenFileDialogDsl : ControlDsl {
  controls : {
      "Ok button" : { $('#OpenFileOkButton') }
  }
}

class RootDsl : ControlDsl {
  controls : {
      "New File dialog box" : { $('#NewFileDialog') }
      "Open File dialog box" : { $('#NewFileDialog') }
  }
}

Шаблон Scope


Симптом: Для сущностей, находящихся внутри иерархии сущностей на ненулевой глубине доступ осуществляется через Inside несмотря на то что существует только одна сущность с данным именем.

Способ решения: Использовать имена только относительно текущего контекста и менять контекст по ходу сценария если количество действий в другом контексте будет более одного.

Результат: Укорочение сценариев, избегание длинных строк имен элементов.

Применение шаблона:

Scenario outline: user should see New File dialog when selects File/New menu item
  Given Trader Application is started
  When user clicks on File menu item
  And user clicks on File/New menu item
  And New File dialog box is opened   <-- шаг меняет контекст на диалог
  Then user clicks on Ok button  <-- кнопка точно идентифицируется, несмотря на то что в системе может существовать несколько регистраций Ok button

Groovy part:
When(~'user clicks on (.+)') { ControlDsl control ->
  dsl.displayed.shouldBecome(true)
  dsl.click()
}

Registration part:

class NewFileDialogDsl : ControlDsl {
  controls : {
      "Ok button" : { $('#NewFileOkButton') }
  }
}

class OpenFileDialogDsl : ControlDsl {
  controls : {
      "Ok button" : { $('#OpenFileOkButton') }
  }
}

class RootDsl : ControlDsl {
  controls : {
      "New File dialog box" : { $('#NewFileDialog') }
      "Open File dialog box" : { $('#NewFileDialog') }
  }
}

Шаблон Resolver


Симптом: Резолвинг сущностей по им строковым представлениям (параметрам имплементаций шагов) осуществляется внутри методов реализаций шагов.

Способ решения: Вынести код резолвинга сущностей по именам в общий код обеспечив автоматическую трансляцию в конечные типы.

Результат: Укорочение и унификация реализаций шагов, включая все проверки. Повышение отказоусточивости исполнения шагов.

Применение шаблона:

Scenario outline: user should see New File dialog when selects File/New menu item
  Given Trader Application is started
  When user clicks on 5th menu item
  Then Save As dialog box should be displayed 

Groovy part:
When(~'user clicks on (.+)') { ControlDsl control ->
  dsl.displayed.shouldBecome(true)
  dsl.click()
}

Registration part:

class NewFileDialogDsl : ControlDsl {
  controls : {
      (~"(.+) menu item") : { Integer index -> $('.Menu').at(index) }
  }
}

Например, автоконвертация String->Integer позволяет, например, записывать позиции элементов текстом:
 - 1st menu item / first menu item
 - 2nd menu item / second menu item
 - 3rd menu item / third menu item
 - #th menu item

Шаблон Readability


Симптом: Код сценариев сложно читается ввиду использования околопрограммистких значений (числа («1») вместо слов («first»)).

Способ решения: Реализовать автоматическую конвертацию из формата, понятному человеку во внутренние форматы, с которыми удобно работать с автоматической проверкой корректности ввода.

Результат: Унификация записи сценариев. Сценарии более понятны бизнес-пользователям, сторонним людям и новым членам команды.

Применение шаблона:

Конвертации:
 - should/shouldn't/should not -> Boolean
 - can/cannot -> Boolean
 - 0,1,2,3 / 1st,2nd,3rd,4th / first,second,third,fourth

Шаблон Names Postfix


Симптом: Код сценариев не содержит определения типов сущностей в именах сущностей, затрудняя понимание, над чем производится действие.

Способ решения: Дописывать к именам сущностей при их регистрации их тип (button/checkbox/message box/...).

Результат: Моментальное понимание типов элементов, состава элементов, над которыми идут действия по беглому взгляду по сценарию.

Применение шаблона:

 - Ok button
 - Save button
 - New File message box
 - No Way radio button

Шаблон Variable


Симптом: Код сценариев зависит от внешних факторов, которые необходимо использовать как в действиях, так и в проверках.

Способ решения: Создание переменных, которые хранят значения внешних факторов.

Результат: Параметризованные сценарии, которые содержат настраиваемые значения либо значения, которые не зависят от сценария напрямую.

Применение шаблона:

Трансформации String->String
 - '{{today}}' -> '27 Jan 2016'         // динамически вычисляется
 - '{{yesterday}}' -> '26 Jan 2016'     // динамически вычисляется
 - '{{today+7}}' -> '03 Feb 2016'       // динамически вычисляется
 - '{{user.email}}' - 'FooFoo@mail.com' // хранится отдельно в test.config, можно менять параметром в TeamCity
 - '{{user.password}}' - 't0psecret'    // хранится отдельно в test.config, можно менять параметром в TeamCity

 And user enters '{{user.email}}' into Login field
 And user enters '{{user.password}}' into Password field

Дальнейшая работа


В дальнейшем планируется расширять паттерны проектирования Cucumber BDD кода путем расширения данной статьи и публикаций дополнений к ней. Конечно же, принимаются комментарии и дополнения в виде ваших паттернов поектирования.

Комментарии (0)