Использование cucumber в качестве движка бизнес правил

    В данной статье я бы хотел поделиться с Вами идеей использования cucumber в качестве движка бизнес-правил и подходом к проверке таких правил.


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


    • для клиента должна быть выбрана группа согласно установленным правилам распределения
    • для каждого клиента должна быть выбрана только одна группа

    Клиенту могут быть присущи такие параметры: страна, идентификатор, язык и т.д.


    Cucumber is a tool that supports Behaviour-Driven Development (BDD) — a software development process that aims to enhance software quality and reduce maintenance costs.
    Gherkin is a Business Readable, Domain Specific Language that lets you describe software's behaviour without detailing how that behaviour is implemented.


    Причины


    Причины побудившие заняться этим вопросом представлены ниже и я их даже пронумеровал, чтобы показать, что их 3.


    Причина 1


    Во многих проектах, где я принимаю участие, используется drools. Правила строго покрываются unit тестами, а кое где даже используются BDD (Behaviour Driven Development) тесты с помощью cucumber. В одном из таких проектов я заметил странную штуку — код BDD тестов сильно походит на код drools правил.
    Приведу пример. Код на drools выглядит так:


    rule "For client from country ENG group will be England"
    when
        Client(country == "ENG")
    then
        insert(new Group("England"));
    end

    Описание фичи (cucumber feature на языке gherkin) в BDD тесте выглядит так:


      Scenario: For client from country ENG group will be England
         When client's country is "ENG"
         Then group will be "England"

    Если абстрагироваться от синтаксиса — один в один! Пример по большей части синтетический, но тем не менее он честный — в реальном проекте все так и сложилось. По факту, наши ожидания описаны в двух местах двумя разными способами и кажется, что второй (gherkin) явно человекопонятнее. Так почему бы сразу не писать на нем?


    Причина 2


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


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


    • реализовать dsl с упрощенным синтаксисом
    • использовать disicion tables

    При этом таблицы — это другой уровень абстракции и больше конфигурация, чем имплементации через бизнес правила.
    Drools dsl позволяет сделать так, что вместо


    when
        Client(country == "ENG")
    then
        insert(new Group("England"));
    end

    можно написать так


    when
        client's country is "ENG"
    then
        group will be "England"
    end

    Dsl — это правильный путь и он лежит в ту степь, где уже пасется cucumber с своим gherkin.


    Причина 3


    Данная причина связана с проблемой тестирования — не понятно, как заказчик будет тестировать правила, написанные собственноручно. Идеально — писать тесты, но я в это верю еще меньше, чем в заказчика, пишущего на drools. Минимум, что нужно сделать в этом случае программисту — предоставить общие тесты базовой логики. Например для нашего приложения описаны требования:


    • для клиента должна быть выбрана группа согласно установленным правилам распределения
    • для каждого клиента должна быть выбрана только одна группа

    и получается, что не зависимо от того, какие правила распределения будут написаны заказчиком, эти требования должны выполняться всегда. И мы можем это проверить так:


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

    Реализация


    Сucumber не заточен под использование вне тестовой среды, но уговорами и угрозами удалось заставить его работать. Пример proof of concept можно посмотреть тут https://github.com/avvero/crools. Собственно, cucumber в качестве движка бизнес правил использовать можно. That's all Folks!


    Тестирование


    Для нашего приложения нужно протестировать следующее:


    1. для клиента должна быть выбрана группа согласно установленным правилам распределения
    2. для каждого клиента должна быть выбрана только одна группа
    3. для набора параметров А выбирается группа Б

    При этом 1 и 2 пункт — это общие требования, они не зависят от конкретных правил распределения, которые имплементировал/имплементирует заказчик, поэтому подготовить тесты для проверки их выполнения можно заранее и прогонять их при изменения правил распределения. Я предлагаю делать это через формирование датасета — набора всех комбинаций возможных вариантов параметров. Строится он интуитивно понятно и благодаря текущей реализации cucumber довольно просто. Почему интуитивно понятно? Приведу пример.


    Если взять условие:


    client's country is 'RUS'

    , то я бы написал unit-тест для таких значений страны:


    • null
    • RUS
    • undefined

    Если взять условия:


    client's country is 'RUS'
    client's country is 'CHL'

    , то я бы написал unit-тест для таких значений страны:


    • null
    • RUS
    • CHL
    • undefined

    Если взять условие:


    client's payment > '1000'

    , то я бы написал unit-тест для таких значений:


    • null
    • 0
    • 999
    • 1000
    • 1001
    • -1000

    Таким образом все необходимые варианты для параметров можно получить из самих правил! А комбинации вариантов можно получить просто перемешиванием.


    Для тех, кто не знаком с cucumber

    Правила, описываемые в feature файле (на языке gherkin), должны быть поддержаны в definition файле (я использую java реализацию). Поддержка в данном случае — это возможность сопоставить (через регулярку) запись из feature файла методу из definition файла, иначе запись будет не понятна и не будет обработана (либо ошибка, либо игнорирование целого правила). Например для


      Scenario: For client from country ENG group will be England
         When client's country is "ENG"
         Then group will be "England"

    должны быть описаны методы


        private Set<String> groups = new HashSet<>();
    
        @When("^client country is \"([^\"]*)\"$")
        public void clientCountryIs(String code) throws Throwable {
            Assert.isTrue(code.equals(client.getCountry()));
        }
        @Then("^group will be \"([^\"]*)\"$")
        public void groupWillBe(String code) throws Throwable {
            groups.add(code);
        }

    Получается, что в definition файле описываются все возможные выражения, которые можно применять при описании сценариев.


    Получение вариантов параметров можно реализовать путем описания дополнительного definition файла с реализацией методов таким образом


        @When("^client's country is \"([^\"]*)\"$")
        public void clientCountryIs(String code) throws Throwable {
            factDictionary.getCountries().add(null);
            factDictionary.getCountries().add(code);
            factDictionary.getCountries().add(UNDEFINED);
        }

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


    Давайте разберем пару примеров такого тестирования (можно самостоятельно через демку http://avvero.pw/crools/)


    Пример 1


    Feature: Select group
      Scenario: England
         When client country is "ENG"
         Then group will be "England"

    Результат проверки


    Some entries have not been distributed:
    #0 {"client":{},"deposit":{}}
    #1 {"client":{"country":"any"},"deposit":{}}

    Значит нужно добавить еще одно правило


    Feature: Select group
      Scenario: England
         When client country is "ENG"
         Then group will be "England"
      Scenario: Default
         When client country is not "ENG"
         Then group will be "Default"

    Пример 2


    Feature: Select group
      Scenario: England
         When client country is "ENG"
         Then group will be "England"
      Scenario: Russia
         When client country is "RUS"
         Then group will be "Russia"
      Scenario: RichRussia
         When client country is "RUS"
         And deposit >= 1000
         Then group will be "RichRussia"

    Результат проверки


    Some entries have not been distributed
    #0 {"client":{"country":"any"},"deposit":{"amount":999}}
    #1 {"client":{},"deposit":{"amount":1001}}
    #2 {"client":{"country":"any"},"deposit":{"amount":1001}}
    ...
    Some entries have been distributed to more than one group
    {"client":{"country":"RUS"}, {"amount":1001}}: Russia, RichRussia

    Исправим проблему с попаданием в более чем одну группу добавив условие And deposit < 1000


      Scenario: Russia (no deposit)
         When client country is "RUS"
         And deposit is null
         Then group will be "Russia"
      Scenario: Russia
         When client country is "RUS"
         And deposit < 1000
         Then group will be "Russia"

    А проблему с отсутствием правил для части вариантов таким правилом


       Scenario: Default
         When client country not in
         |ENG|
         |RUS|
         Then group will be "Default"

    Получается, что "хороший" набор правил будет выглядеть так


     Feature: Select group
      Scenario: England
         When client country is "ENG"
         Then group will be "England"
      Scenario: Russia (no deposit)
         When client country is "RUS"
         And deposit is null
         Then group will be "Russia"
      Scenario: Russia
         When client country is "RUS"
         And deposit < 1000
         Then group will be "Russia"
      Scenario: RichRussia
         When client country is "RUS"
         And deposit >= 1000
         Then group will be "RichRussia"
      Scenario: Default
         When client country not in
         |ENG|
         |RUS|
         Then group will be "Default"

    Таким образом сформированный датасет дает нам представление о множестве значений параметров и их комбинаций. И на основе него мы можем уже делать выводы о том, как наши правила распределения работают. И самое главное — показать это заказчику в gui в режиме реального времени во время правки.
    Что же касается оставшейся проверки


    3 для набора параметров А выбирается группа Б

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


    В зависимости от требований и прочих условий (типа "я могу предположить возможные сценарии использования заказчиком") можно добавить и другие проверки на основе датасета.
    Проверки типа "а точно ли клиент из России с депозитом 2000 попадет в группу RichRussia?" можно осуществить так — посмотреть (предварительно предоставив GUI для такого дела) какие клиенты попали в RichRussia или куда попали клиенты из России с депозитом больше 1000.

    • +12
    • 2,6k
    • 7
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 7
    • 0
      Таки у вас кто-то кроме разработчика реально так пишет?
      Или всё равно разработчик добивает это до реально рабочего состояния?
      • 0
        В основном правила придумывают (на основе требований) и имплементируют только разработчики, но вместе с задачей иногда предоставляются и acceptance criteria в нотации Given-When-Then, а это уже полдела и остается только заимплементировать.
        • 0
          Ну таки да, в этом вся печаль. Основная идея BDD то в первую очередь в том что требования пишут те, кому они ближе, а на деле всё равно разработчики.

          Но, в любом случае интересный опыт, спасибо =)
          • 0
            Я наверное не правильно понял Ваш изначальный вопрос, описанное в статье решение — это proof of concept, рабочего и пользуемого приложения нет. Но идея как раз в том, что модифицировать правила должен именно «заказчик», а программист только бы расширял словарь «фраз»
            • 0
              Крайне суровые аналитики, конечно, могут писать BDD-тесты, но ввиду их относительной редкости, BDD-тесты чаще пишут разработчики/тестировщики в зависимости от особенностей команды. А BDD-тесты выступают человеко-понятными acceptance criteria с одной стороны, и машино-читаемыми интерфейсами к внутренностям тестов с другой стороны. И тогда BDD-тесты могут закрыть вопроса: А как работает сейчас (работало вчера, будет работать завтра — относительно простым человеческим языком)? И если тест упал, то как происходит декомпозиция в непосредственно реализацию набора действий?
          • +1
            У нас реально тестировщики пишут сценари в BDD тестах (мы используем jBehaive).
            А идея рулов на основе BDD сценариев интересная: человеческий язык всегда ближе. Вопрос только, как с теми же drools — производительность и надежность. BDD движки не расчитаны на производительную работу. Мы столкнулись с утечками памяти, проблемами в параллельной работе и т.п.
            • 0
              Вы конечно же правы, надежность cucumber при использовании в продакшене вызывает вопросы. Но внушает надежду тот факт, что имплементация cucumber (я видел конечно же только java) в принципе не шибко сложная — один только подход к разбору feature диво простой, поэтому шансы поправить что-то самостоятельно высоки.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.