BDD и алгоритмы — как делается правильный переход?

Взяться меня за этот флеймовый, наверное, вопрос заставили наблюдения за внедрением #bdd и его, гм, языкового несоотвествия задачам, которые возникают в тестировании . Всё это, простите, с моей колокольни весьма походит на анекдот “мышки плакали, кололись, но продолжали жрать кактус”.

Язык #bdd сверх меры упрощён, и не включает совершенно простейших алгоритмических конструкций, известных мне ещё по журналу “Юный техник”, например “повторить шаг 5”, “если выполняется условие X, то сделать Y иначе сделать Z” или “пока не выполняется условие У, делать инструкции 3-7” и т.д.

Я видел людей, которые на полном серьёзе пытаются описывать не только задачи, но и сценарии через Given - When - Then на каком-нибудь #jbehave , и потом влетают именно в это: автоматизация требует алгоритмического языка, а алгоритмическому языку Given - When - Then нужны как пятое колесо в телеге — ему нужны условные операторы, переходы и последующие сборки из них (имея условный оператор и оператор перехода “собрать” цикл уже можно).

Например, задача прочтения нескольких писем в почтовом ящике —
с помощью условий-переходов или цикла мы можем написать

X. Открыть непрочтённое письмо
X+1. Закрыть письмо
X+2. Проверить что счётчик непрочтённых писем уменьшился на 1
X+3. Если счётчик непрочтённых писем > 0 , перейти к шагу X, иначе к следующему

или

ПОКА можно найти непрочтённое письмо
Открыть непрочтённое письмо
Закрыть письмо
Конец цикла ПОКА

А если есть только Given-When-Then , то что? :smile:

Возникали ли такие проблемы у вас, как вы их решали?
Придуманы ли какие-то средства перехода от #bdd к нормальному алгоритмическому описанию?
Ну и (флеймовый вопрос): в чём сакральный смысл описания задач через #bdd если нормально описывать решение задач через #bdd невозможно?

Лучше начать с конца. С технической точки зрения BDD - это одна из реализаций Keyword Driven Development подхода, при котором некоторой удобной для чтения фразе сопоставляется некоторый фрагмент кода автотеста. К тому же в случае данной реализации JBehave или более распространенной Cucumber, этот подход еще и расширен возможностью использования одновременно и Data-Driven подхода (см. ключевое слово Examples).

Для чего это было изначально придумано? В-основном, это очередная попытка совместить тест-дизайн и автоматизацию этих самых тестов, что позволяет схлопнуть эти две стадии в одну. При этом, за счет того, что те или иные фразы по мере наращивания базы ключевых слов переиспользуются множество раз, формируя некоторый DSL, объем работ по автоматизации (непосредственно написании программного кода) не зависит напрямую от количества тестовых сценариев, которые разрабатываются на стадии тест-дизайна. Со временем это может вырасти в то, что тесты автоматизируются сразу же во время тест-дизайна без написания дополнительного программного кода. Соответственно, автоматизацию можно в значительной мере делегировать нетехническим специалистам, о чем, наверное, до сих пор мечтает отдел маркетинга различных вендоров, которые предоставляют платные тулы для автоматизации.

Для автоматизации, это может быть и нужно, а вот для тест-дизайна - не очень, так как к нему немного другие требования, основанные на немного других принципах.

Для тест-дизайна, особенно пользовательских сценариев подобные вещи скорее вредны, так как, как минимум, нарушается детерминированность теста. То есть, если у нас свалился тест и нам надо посмотреть, что же с ним пошло не так, то у нас нет гарантии, что он отработает точно так же, как и там, где он упал. А если вдруг и отработал при повторном запуске, то не факт, что он пошел по тому же самому пути. Не забывайте, что при тестировании имеет важное значение не только функционал в целом, но и последовательность действий, которые тестом проверяются.

Но вышеперечисленные вещи отлично делаются при правильном дизайне тестовых сценариев. Например:

Подобную конструкцию при дизайне тестов не очень хорошо использовать. Это примерно, как использование оператора goto. Можно, но это не слишком приветствуется. В принципе, ничто не мешает продублировать этот самый шаг 5.

Подобная конструкция нарушает воспроизводимость теста и, по-хорошему, это должно быть 2 линейных сценария, которые отдельно проведут действие Y, при этом сделано все, чтобы выполнилось условие Х. И второй тест выполняет действие Z. Иначе, если оставить описание вот в такой же формулировке, то может оказаться, что тест всегда выполняет действие Y и никогда не делает Z. Результат - действие Z не проверяется никогда. Отчасти поэтому желательно тестовые сценарии делать максимально линейными, хотя бы на самом верхнем уровне. Так мы хотя бы знаем, какая функциональность гарантированно покрывается тем или иным тестом. Если действия однотипные, то данный набор тестов можно завернуть в Data-Driven, чтобы сделать сценарии более компактными.

Аналогично предыдущему. Тесты, в отличие от реализации алгоритма, не должны реализовывать некоторую бизнесс-логику. Они должны уметь детерминированно воспроизводить определенный сценарий с конкретной последовательностью шагов, чтобы мы могли эти шаги воспроизвести в точности, не вдаваясь в детали технической реализации конкретного автотеста. Не забывайте, что вам надо в первую очередь проверить приложение, а не продублировать его реализацию.

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

Реально же с BDD проблемы могут возникнуть, когда мы работаем с некоторыми величинами, которые формируются динамически и\или надо передать данные между шагами. Это не блокирующая проблема, но её решение обычно выглядит скорее как костыль.
Но это то, что надо понимать и иметь ввиду.

А касательно вот этого:

Безусловно. Могу привести несколько примеров: Java, C#, JScript, Python, Ruby, xUnit-движки - все, что позволяет писать полноценные конструкции условия, циклов и т.п (да и языки вполне себе спроектированы так, чтобы можно было описывать алгоритмы в формализированном, компактном виде). Если вам нужны такие конструкции, то зачем вам какой-то BDD, особенно если никакими преимуществами данного подхода вы пользоваться не собираетесь?
Да и сам подход не везде имеет смысл применять. В ряде случаев это реально лишняя трата времени.

2 лайка

Просто поднимитесь на уровень выше, в BDD-сценарии достаточно написать

Given a list of messages in a mailbox

а всю алгоритмическую механику спрятать на уровень реализации этого шага.

3 лайка

Спасибо за подробный ответ!

Ну, во-первых, как я уже говорил, я видел людей, которые пытались всё описывать через Given-When-Then , и с их помощью получил, так сказать, печального опыта. Сам-то я более чем согласен, что BDD не везде применимо, но, согласитесь, кучу рекламы этому подходу тоже делают.

Во-вторых, этот печальный опыт тоже поначалу включал иллюзии о делегировании автоматизации неспециалистам и пр. — но реальный мир и реальные системы выступили совершенно против этих иллюзий. В настоящее время я склоняюсь к утверждению “если вы не можете описать поведение системы на достаточно сложном языке, то написать хороших тестов для неё вы тоже не сможете” .

В-третьих, системы реального мира далеко не всегда ведут себя детерминированно —
оставаясь при этом тестабельными (и автоматизируемыми). Тот же пример из почтового ящика, в котором может быть случайное количество писем.

В-четвёртых, Ваш текст самсначала говорит об очередной попытке “совместить тест-дизайн и автоматизацию этих самых тестов”, но потом подтверждает противоречия между ними 1) “Для автоматизации, это может быть и нужно, а вот для тест-дизайна - не очень” 2) “Для тест-дизайна, особенно пользовательских сценариев подобные вещи скорее вредны”
Вот об этом противоречии, собственно, и моя данная тема.

Я готов признать BDD более чем пригодным для систем “нулевого порядка” (со всегда одним и тем же результатом типа “Hello, World”) или систем “первого порядка” когда результат, однозначно зависим от вводимого параметра (допустим, программа реализует возведение в квадрат, ха-ха).

Но ведь системы реального мира намного сложнее, и я так вижу, что подход, который называет себя “Behavior” для описания behavior-поведения реально существующих систем просто непригоден, если эти системы выходят за рамки линейных и предсказуемых.

В-пятых, у меня есть определённый опыт совершенно нелинейных автоматических тестов с описанием их полезности и практического применения, и был даже собравший немножко позитивных отзывов популярный доклад об этом — atdays.com : Обезьянье тестирование в мобильных проектах - YouTube

Но, видимо, всё это, с примерами нелинейности, стоит изложить в отдельном тексте :wink:

Если состояние системы не может быть определено - она не может быть протестирована, в том числе и вручную. Ваш пример с почтовым ящиком - просто пример плохого тест-дизайна.
Тесты должны быть линейными - примите это как аксиому.Если тест нелинеен, то, либо у вас баги в требованиях, либо необходим редизайн/декомпозиция.
А зачем вы стохастическое тестирование сюда приплели - вообще не понятно.

2 лайка

Вы совершенно неправы :smile:

Пример системы из реального мира (войдёт в будущий текст “Реальный мир против BDD и линейных схем”).

Есть автотесты для сайта. Допустим, магазина. После авторизации Вы можете получить от одного до трёх попапов. (поздравление с праздником, акция, информационное сообщение о даунтайме, личное сообщение).

Наличие или отсутствие попапов (то есть состояние системы после логина) не может быть определено заранее. Как минимум оно предполагает несколько вариантов. Значит ли это, что этот сайт нельзя протестировать? Ну конечно же нет!

Пример второй (для абстракции концепция взята из запроса на форум поддержки Sikuli):

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

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

Значит ли что это нельзя протестировать? Смешной вопрос.

Такая “аксиоматика” совершенно не соответствует реальному миру. :smile: В реальном мире происходят случайные события, есть ветвящиеся сценарии и сценарии, требующие циклических действий :wink:

Вы смешиваете возможные трудности и ньюансы при автоматизации с линейностью тест-кейсов

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

Опять не вижу тут ничего нелинейного. Два теста: первый - что ресурс можно собрать, второй - что противник появляется и его можно убить.
Повторюсь - вы говорите про сложности реализации в автоматизации, но почему-то выдаете их за нелинейность тест-кейсов.
Это разные плоскости. Абсолютно.

Given игрок в локации X
When появился моб Y
And игрок убил моба Y
Then игрок получил Z экспы

Как вы будете автоматизировать"When" - это не относится ни к тест-кейсу, не к BDD. Но сам кейс линеен.

Хм.

  1. Видите ли, даже тема называется “BDD и алгоритмы — как делается переход?”. Переход, понимаете? Это вот как раз переход от абстрактных линейных описаний к автоматизации тестирования тех систем реального мира, в которых происходят случайные события.
  2. Вы, кажется, начали с “Если состояние системы не может быть определено - она не может быть протестирована, в том числе и вручную.” И я Вам привёл примеры неопределённости в состояниях системы, которое остаётся неопределённым, несмотря на линейность описания. Если Вам мало, я ещё найду. Хоть до тестирования генераторов случайных чисел (такое тоже есть).

Поведение систем в реальном мире далеко не всегда линейно, и практика сценариев автоматизации отличается от линейных теорий. В ряде случаев линейное описание не соответствует нелинейному поведению/исполнению, которое требует тех алгоритмических действий, о которых я говорю — ожидание (через цикл), ветвление (условные операторы) и пр.

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

Вы разве не согласны с тем, что практическая польза извлекается из практического тестирования системы на соответствие тем или иным требованиям, а не линейных описаний? :smile:

В чём Вы видите пользу попыток доказывания мне что “описания всегда линейны”? Потребность в переходе к нелинейным сценариям автоматизации от этого никуда не пропадает.

Я полностью потерял суть вашего повествования. Вы сетуете на то, что описания системы линейны, а при её автоматизации могут потребоваться ветвления?
Хотите вынести детали реализации в описание?

Коллеги, прежде чем жаловаться на BDD, я рекомендую посетить вот этот сайт:

http://behaviour-driven.org/

почитать внимательно про назначение BDD, и может быть вопросы про его непригодность для описания алгоритмов отпадут сами собой. Да, непригоден. Потому что и не предназначен.

1 лайк

Я видел как Given When Then попали в реализацию на уровень низовых кейвордов.

When Click Image1
When Click Image2
When Wait 20
Then Image 2 Should Be Present

Данный фрагмент выглядит разумно, но там, где низовая реализация требует алгоритмических действий, Given When Then для языка описания реализации явно не хватает, о чём я, собственно, и пишу.

Если я правильно Вас понял, значит те, у кого я увидел перенос языка описания Given-When-Then в шаги реализации, поступили неправильно, и они там, в механике , вообще лишние, а должны существовать исключительно на верхнем уровне, лишь описывая происходящее “в общих чертах”, но не затрагивая реализацию вообще.

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

“Правильные” реализации BDD и “неправильные”.

Если я правильно понял ответы, мои вопросы возникли из-за того, что мне не повезло нарваться на весьма своеобразную реализацию, где Given-When-Then используют для того, для чего они вообще не предназначены. Хотелось бы увидеть правильные.

Верно. BDD story по сути есть тест-кейс написанный на DSL, ёмкий и упрощенный до минимума, оставаясь при этом понятным всей команде - Given=Precond, When=Step, Then=Actual_Result. А в тест-кейсе не может быть ветвлений.

Если абстрагироваться от BDD, это стандартные грабли Keyword-Driven-Framework - попытка вынести алгоритмы реализации за пределы кода. Простых if/else/for/while все-равно будет не достаточно, а “читабельность” будет никакая. Представьте что у jBehave есть эти “ифы” и надо проверить, например, сортировку в гриде - и мы приходим к тому, что нужны операторы сравнения, переменные, типы данных и т.д.

Ок, пришли к какому-то общему пониманию.

А ответ на свой вопрос по примеру, я, кажется, нашёл тут:
https://bitbucket.org/robotframework/robotdemo/src/master/gherkin.txt

Итого (с моей стороны)

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

BDD, Keyword-Driven - это всего лишь абстракции. Gherkin - это вообще 4 слова. Думать, что при помощи 4-х слов можно написать тест, это все равно что пытаться писать поэму в twitter. В частности в robot framework я взаимодействие с системой пишу в python, тяжелую логику тестов на нем же, а описываю тесты на keyword driven (называю это фасадом, за котором скрыта логика). Переход, о котором спрашивал автор, реализует фреймворк.

1 лайк