Behavior-Driven Development
с помощью Cucumber

Андрей Михайлов (lolmaus)

Frotnend-разработчик в Kaliber5

 

https://lolma.us

https://github.com/lolmaus

https://kaliber5.de

или как не подавиться огурцом

Behavior-Driven Development

assert.isEqual(actual, expected)
expect(actual).to.equal(expected)
actual.should.equal(expected)

Behavior-Driven Development

TDD

Пишем

падающий

юнит-тест

Пишем

код

Unit-тест

проходит

Рефакторим

Пишем падающий сценарий

Рефакторим

Сценарий проходит

Behavior-Driven Development

  • Включает всех членов команды, в том числе непрограммистов:
    • UX-специалист
    • дизайнер
    • менеджер
    • представитель заказчика
    • тимлид
       
  • Вращается вокруг сценариев (user stories)
    • Написаны на человеческом языке
    • Описывают фичу с разных сторон
    • Покрывают как основные,так и пограничные случаи

User stories для регистрации/логина

  • Посещение разделов:
    • Анонимный пользователь посещает публичный раздел приложения, переходит с нее на страницу логина по ссылке.
    • Анонимный пользователь посещает непубличный раздел приложения, и его перенаправляет на страницу логина.
    • Залогиненный пользователь посещает непубличный раздел приложения.
    • Залогиненный пользователь посещает страницу логина, и его перенаправляет в непубличный раздел приложения.
    • Залогиненный пользователь посещает страницу регистрации, и его перенаправляет в непубличный раздел приложения.

 

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

Преимущества
сценариев и BDD

  • Вскрывается истинный объем фич
  • Более реалистичная оценка сроков
  • Сценарии выполняют роль ТЗ
  • Написаны на человеческом языке
  • Служат источником правды для разрешения разногласий
  • Разработчикам легко контролировать объем выполнения фич
  • Сценарии фокусируются на нуждах пользователей
  • Интегрируются с автоматическим тестированием
  • Появился 10 лет назад как библиотека поверх RSpec на языке Ruby.
  • Развился в полноценную методологию.
  • Портирован на многие языки. Есть официальные порты от команды Cucumber и неофициальные аналоги.
  • На JS есть официальный CucumberJS и неофициальный Yadda.

Feature-файл Cucumber

Feature: The blog.



Scenario: Visiting the blog with posts
    Given there are 3 posts in the database
    When I visit the blog page
    Then I should see 3 posts
    And I should not see the empty blog message
    


Scenario: Visiting an empty blog
    Given there are 0 blog posts in the database
    When I visit the blog page
    Then I should see 0 posts
    And I should see the empty blog message
  • Separation of concerns
  • Простота валидации
  • Переиспользование кода (DRY)
  • Читаемость тестов
  • Упрощение поддержки кода тестов
  • Интеграция в рабочий процесс
  • Дисциплина
  • Code coverage
  • Унификация кода
  • Скорость написания тестов
  • Вовлечение всех членов команды

Преимущества Cucumber

Проблемы Cucumber

  1. Библиотека имплементаций шагов растет бесконтрольно.

    Ориентироваться в ней крайне трудно.

Проблемы Cucumber

  1. Библиотека имплементаций шагов растет бесконтрольно.
    Ориентироваться в ней крайне трудно.
     
  2. Каждый шаг — это черный ящик.
Scenario: Viewing blog posts
    Given there are 3 posts in the database
    When I visit the blog
    Then the title of the 1st post should be "Hello World"

Проблемы Cucumber

  1. Библиотека имплементаций шагов растет бесконтрольно.
    Ориентироваться в ней крайне трудно.
     
  2. Каждый шаг — это черный ящик.
     
  3. Шаги имеют скрытые зависимости друг от друга.
    When I expand the 2nd post
    Then the expanded post should be expanded

Проблемы Cucumber

  1. Библиотека имплементаций шагов растет бесконтрольно.
    Ориентироваться в ней крайне трудно.
     
  2. Каждый шаг — это черный ящик.
     
  3. Шаги имеют скрытые зависимости друг от друга.
     
  4. Использование CSS-селекторов контпродуктивно.
When I click on div p ul li:nth-child(2) a

Проблемы Cucumber

  1. Библиотека имплементаций шагов растет бесконтрольно.
    Ориентироваться в ней крайне трудно.
     
  2. Каждый шаг — это черный ящик.
     
  3. Шаги имеют скрытые зависимости друг от друга.
     
  4. Использование CSS-селекторов контпродуктивно.
     
  5. Сложности с отладкой.

Решения

  1. Ограничиваемся небольшим количеством универсальных шагов.

    Например:
  • клик
  • ввод текста
  • наведение мышкой
  • чтение текста со страницы
  • чтение текста из поле ввода
  • выбор элемента из выпдающего списка

Решения

  1. Ограничиваемся небольшим количеством универсальных шагов.
     
  2. Выносим "правду" из имплементаций в сценарии.

Решения

  1. Ограничиваемся небольшим количеством универсальных шагов.
     
  2. Выносим "правду" из имплементаций в сценарии.
     
  3. Шаги ссылаются друг на друга в тексте сценариев, а не внутри имплементаций.

Было:

Then the 1st product should be selected

Стало:

Then ".product:nth-child(1)" should have HTML class "is-selected"
And I should see ".product:nth-child(1) .comments"

Было:

Given there are 2 posts in the database
# ...
Then the title of the 1st post should be "Hello world!"

Стало:

Given there are records of type "Post" in the database:
    ---------------------------------------------------
    | id  | title          | body          | authorId |
    | "1" | "Hello world!" | "Lorem ipsum" | "1"      |
    | "2" | "Foo Bar"      | "Lorem ipsum" | "1"      |
    ---------------------------------------------------

# ...
Then the title of the 1st post should be "Hello world"

Решения

  1. Ограничиваемся небольшим количеством универсальных шагов.
     
  2. Выносим "правду" из имплементаций в сценарии.
     
  3. Шаги ссылаются друг на друга в тексте сценариев, а не внутри имплементаций.
     
  4. Используем только семантические селекторы с удобным, но намеренно ограниченным синтаксисом.

Было:

When I click on div p ul li:nth-child(2) a

Стало:

When I click on "[data-test-post]:nth-child(2) [data-test-title]"

4. Используем только семантические селекторы

[data-test-main-menu]
 data-test-main-menu
           main-menu
           Main-Menu

4. Используем только семантические селекторы с удобным, но намеренно ограниченным синтаксисом

When I click [data-test-post]:nth-child(2) [data-test-expand-button]

4. Используем только семантические селекторы с удобным, но намеренно ограниченным синтаксисом

When I click the Expand-Button in the 2nd Post

Было:

Стало:

<div class="container">
    <article data-test-post>
    </article>
</div>
    
    
<div class="container">
    <article data-test-post>
    </article>

    <article data-test-post>
    </article>
 </div>
    
    
<div class="container">
    <article data-test-post>
    </article>

    <article data-test-post>
    </article>
 </div>

Проблема :nth-child()

2nd Post

[data-test-post]

:nth-child(2)

<div class="container">
    <article data-test-post>
    </article>
</div>
    
    
<div class="container">
    <article data-test-post>
    </article>

    <article data-test-post>
    </article>
 </div>
    
    
<div class="container">
    <article data-test-post>
    </article>

    <article data-test-post>
    </article>
 </div>

Решение: селектор :eq(2) из jQuery

2nd Post

[data-test-post]

:eq(2)

1

2

3

4

5

Решения

  1. Ограничиваемся небольшим количеством универсальных шагов.
     
  2. Выносим "правду" из имплементаций в сценарии.
     
  3. Шаги ссылаются друг на друга в тексте сценариев, а не внутри имплементаций.
     
  4. Используем только семантические селекторы с удобным, но намеренно ограниченным синтаксисом.
     
  5. Добавляем удобный вывод ошибок.

5. Добавляем удобный вывод ошибок

  • Название шага
  • Имплементация
  • Assertion error
  • Аргументы
  • Стэктрэйс

Всем спасибо! ^_^

http://bit.ly/cucumber-ember-nn-2021

Секретный слайд

Made with Slides.com