Потратив множество человеко-часов над разработкой автотестов для нескольких огромных проектов, я с полной уверенностью могу сообщить, что составил может быть далеко не полный, но уж точно достаточно крупный набор практик, с которыми хочется поделиться с каждым. Итак, следуя стопам классиков, хочу (надеюсь увидеть дополнения от вас в комментариях) составить:
Цели:
Итак, поехали!
Симптом: Есть готовый сценарий работы приложения, но логика в предназначении шагов не верна.
Способ решения: Выстроить сценарий в соответствии с последовательностью: Дано — Действие — Результат.
Результат: Сценарий приобретает логическое разделение на зоны ответственности, становится более понятным стороннему наблюдателю.
Применение шаблона: Рассмотрим на примере:
Содержит действия в Given секции. По рекомендации шаблона проектирования, действия должны находиться в секции When:
Симптом: В каждом сценарии файла проводится одинаковая подготовительная работа по достижению некоторого состояния приложения.
Способ решения: Вынести общий код в секцию Background.
Результат: Каждый сценарий становится лаконичным и делает только те действия, которые написаны только для него. Вынося общий код, становится легкой его будущая поддержка.
Применение шаблона:
Содержит одинаковые предусловия в Given секции. По рекомендации шаблона проектирования, они должны находиться в Background секции.
Симптом: Для одного и того же действия над различными сущностями одного типа определены различные определения шагов действий в их реализации.
Способ решения: Сделать действие общим, принимая сущность как опцию.
Результат: Код сценариев становится более предсказуем и ясным, код реализации действий сокращается до одного метода. Облегчается будущая поддержка кода.
Применение шаблона:
Содержит одинаковый по смыслу код для двух шагов. По рекомендации шаблона проектирования, они должны быть объеденены в один метод.
Симптом: Для различных типов сущностей определены различные определения шагов одних и тех же действий в их реализации
Способ решения: Выделить свойства и действия над сущностями в отдельные интерфейсы, группируя по общим характеристикам. Сами сущности в определениях шагов должны приниматься не через конечные типы, а через интерфейсы.
Результат: Таким образом унифицируется процедура действия над разнотипными сущностями. Далее, чтобы сущность поддержала все необходимые шаги, достаточно реализовать все необходимые интерфейсы. При этом, выделив над сущностями одной функциональной группы базовый класс (например, UI элементы) и реализовав интерфейсы в нем, все наследники автоматически получают поддержку всех шагов над ними.
Применение шаблона:
Содержит одинаковый по смыслу текст шагов, но различаясь в типах параметров содержит разную реализацию.
Симптом: Для сущностей, находящихся в различных частях их иерархии внутри приложения строится плоская иерархия регистраций сущностей в коде обслуживания cucumber bdd и как результат — пересечения имен (Trade Ok button, Save Ok button, Are You Sure Ok button вместо просто Ok button).
Способ решения: Реализовать иерархическую регистрацию сущностей с сохранением информации о вложенности одних сущностей в другие.
Результат: Таким образом унифицируется процедура действия над разнотипными сущностями. Далее, чтобы сущность поддержала все необходимые шаги, достаточно реализовать все необходимые интерфейсы. При этом, выделив над сущностями одной функциональной группы базовый класс (например, UI элементы) и реализовав интерфейсы в нем, все наследники автоматически получают поддержку всех шагов над ними.
Применяется совместно с паттерном Scope.
Применение шаблона:
Симптом: Для сущностей, находящихся внутри иерархии сущностей на ненулевой глубине доступ осуществляется через Inside несмотря на то что существует только одна сущность с данным именем.
Способ решения: Использовать имена только относительно текущего контекста и менять контекст по ходу сценария если количество действий в другом контексте будет более одного.
Результат: Укорочение сценариев, избегание длинных строк имен элементов.
Применение шаблона:
Симптом: Резолвинг сущностей по им строковым представлениям (параметрам имплементаций шагов) осуществляется внутри методов реализаций шагов.
Способ решения: Вынести код резолвинга сущностей по именам в общий код обеспечив автоматическую трансляцию в конечные типы.
Результат: Укорочение и унификация реализаций шагов, включая все проверки. Повышение отказоусточивости исполнения шагов.
Применение шаблона:
Симптом: Код сценариев сложно читается ввиду использования околопрограммистких значений (числа («1») вместо слов («first»)).
Способ решения: Реализовать автоматическую конвертацию из формата, понятному человеку во внутренние форматы, с которыми удобно работать с автоматической проверкой корректности ввода.
Результат: Унификация записи сценариев. Сценарии более понятны бизнес-пользователям, сторонним людям и новым членам команды.
Применение шаблона:
Симптом: Код сценариев не содержит определения типов сущностей в именах сущностей, затрудняя понимание, над чем производится действие.
Способ решения: Дописывать к именам сущностей при их регистрации их тип (button/checkbox/message box/...).
Результат: Моментальное понимание типов элементов, состава элементов, над которыми идут действия по беглому взгляду по сценарию.
Применение шаблона:
Симптом: Код сценариев зависит от внешних факторов, которые необходимо использовать как в действиях, так и в проверках.
Способ решения: Создание переменных, которые хранят значения внешних факторов.
Результат: Параметризованные сценарии, которые содержат настраиваемые значения либо значения, которые не зависят от сценария напрямую.
Применение шаблона:
В дальнейшем планируется расширять паттерны проектирования Cucumber BDD кода путем расширения данной статьи и публикаций дополнений к ней. Конечно же, принимаются комментарии и дополнения в виде ваших паттернов поектирования.
Шаблоны проектирования 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 кода путем расширения данной статьи и публикаций дополнений к ней. Конечно же, принимаются комментарии и дополнения в виде ваших паттернов поектирования.