Патерни і антипаттерны 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 коду шляхом розширення даної статті і публікацій доповнень до неї. Звичайно ж, приймаються коментарі і доповнення у вигляді ваших патернів поектирования.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.