В блоге я уже несколько раз писал о предметно-ориентированных языках (DSL). Эта моя заметка будет куда более приближена к реальной жизни, так что не закрывайте пока страницу.
Не буду повторять сам себя, процитирую Фаулера:
That said, I do think that the greatest potential benefit of DSLs comes when business people participate directly in the writing of the DSL code. The sweet spot, however is in making DSLs business-readable rather than business-writeable. If business people are able to look at the DSL code and understand it, then we can build a deep and rich communication channel between software development and the underlying domain.
Наибольший потенциал DSL он видит в том, что текст на предметно-ориентированном языке могут писать и — главное — читать эксперты предметной области, а вовсе не программисты. Благодаря этому можно обеспечить совершенно другой уровень коммуникации между разработкой ПО и предметной областью.
В этом посте я хотел бы анонсировать библиотеку DelphiSpec. Не буду скрывать, что вдохновил меня проект Cucumber. То есть фактически библиотека является Delphi-реализацией языка Gherkin (переводится как «корнишон»). Реализация не полная (считайте альфа-версией). Некоторых возможностей пока нет, но проект будет развиваться. Мне он интересен.
Если в двух словах техническим языком выразить суть проекта, то DelphiSpec позволяет автоматически превратить подобный сценарий в тест для старого доброго DUnit:
Feature: Calculator Scenario: Add two numbers Given I have entered 50 in calculator And I have entered 70 in calculator When I press Add Then the result should be 120 on the screen Scenario: Add two numbers (fails) Given I have entered 50 in calculator And I have entered 50 in calculator When I press Add Then the result should be 120 on the screen Scenario: Multiply three numbers Given I have entered 5 in calculator And I have entered 5 in calculator And I have entered 4 in calculator WHEN I press mul Then the result should be 100 on the screen
Подчеркну, это не тесты, это сценарии. То есть настоящий Behavior-Driven Development (BDD) в дополнение к известному Test-Driven Development (TDD). Такие сценарии могут использоваться как часть постановки задачи, как часть документации к проекту и конечно как тесты. С помощью сценариев не принято описывать внутренние особенности системы, сценарии описывают как происходящее выглядит для пользователя.
Выбор языка Gherkin в качестве базового для библиотеки DelphiSpec хорош тем, что в определенной нише это вполне себе установившийся стандарт и, заинтересовавшись, вы даже сможете купить книгу с лучшими практиками, советами по написанию сценариев и примерами на этом языке. В интернете информации так же достаточно.
Вернемся к примеру с калькулятором. Результат выполнения сценариев в DUnit будет выглядеть так:
Как это работает? Сам язык Gherkin очень мал. По сути единственная его функция — это описание связок Given-When-Then (по-русски: дано-если-то). Есть несколько ключевых слов, все остальное кастомизируется.
В примере мы тестируем очень простой класс-калькулятор. Здесь я его код выкладывать не буду, смотрите его по ссылке. Интересно другое: как программе понять и выполнить команды вроде «Given I have entered 50 in calculator»? Вот тут в дело вступает вспомогательный класс описывающий эти шаги (так называемые «step definitions»). Выглядит класс так:
unit SampleCalculatorStepDefs; interface uses SampleCalculator, DelphiSpec.Attributes, DelphiSpec.StepDefinitions; type [_Feature('calculator')] TSampleCalculatorSteps = class(TStepDefinitions) private FCalc: TCalculator; public procedure SetUp; override; procedure TearDown; override; [_Given('I have entered (.*) in calculator')] procedure EnterInt(Value: string); [_When('I press Add')] procedure AddInt; [_When('I press Mul')] procedure MulInt; [_Then('the result should be (.*) on the screen')] procedure TestResult(Value: string); end; implementation uses System.SysUtils, TestFramework, DelphiSpec.Core; { TSampleCalculatorSteps } procedure TSampleCalculatorSteps.AddInt; begin FCalc.Add; end; procedure TSampleCalculatorSteps.EnterInt(Value: string); begin FCalc.Push(Value.ToInteger); end; procedure TSampleCalculatorSteps.MulInt; begin FCalc.Mul; end; procedure TSampleCalculatorSteps.SetUp; begin FCalc := TCalculator.Create; end; procedure TSampleCalculatorSteps.TearDown; begin FCalc.Free; end; procedure TSampleCalculatorSteps.TestResult(Value: string); begin if FCalc.Value <> Value.ToInteger then raise ETestFailure.Create('Incorrect result on calculator screen'); end; initialization RegisterStepDefinitionsClass(TSampleCalculatorSteps); end.
То есть по сути у каждого шага есть тип (Given/When/Then) и регулярное выражение, описывающее остальную часть шага. Регулярные выражения помогают выделить из шагов параметры, которые будут переданы в методы класса для выполнения соответствующей части сценария.
Чтобы вся эта система заработала, остается только добавить в dpr-файл вызов процедуры, получающей на вход путь к файлам с описанием сценариев и превращающей эти сценарии в настоящие тесты для DUnit.
begin PrepareDelphiSpecs(TPath.Combine(ExtractFilePath(Application.ExeName), 'features'), True, 'EN'); DUnitTestRunner.RunRegisteredTests; end.
Последний параметр («EN») позволяет указать буквенный код языка, на котором написаны сценарии. Это еще одна интересная возможность Gherkin. Так как в общем-то нет разницы текст на каком языке обрабатывать с помощью регулярных выражений, то грех было не перевести на разные языки и несколько ключевых слов, для чего служит соответствующий XML-файл.
То есть ничто не мешает записывать сценарии даже вот так:
Функционал: Калькулятор Сценарий: Сложить два числа Допустим я ввожу 50 на калькуляторе И я ввожу 70 на калькуляторе Когда я нажимаю СЛОЖИТЬ Тогда результат на экране должен быть 120
Это пока первые шаги, но постепенно я планирую реализовать все возможности Gherkin.
1. IL
18 Дек 2013 11:15 пп
Как же DSL напоминает http://en.wikipedia.org/wiki/FLOW-MATIC
И цель схожая :)
А в тестах выглядит заманчиво.
2. Alex W. Lulin
18 Дек 2013 11:36 пп
Роман!
Офигенски!
Честно.
Прям — «завидую».
Я вот тут написал «про своё» — http://programmingmindstream.blogspot.ru/2013/12/delphispec.html
Если не к месту — удаляй нафик. :-)
А вообще — «жму руку» :-)
Правда — КЛАССНО!
Будут «плеваться» — не слушай. Развивай. Может быть это даже удастся «продать».
3. Alex W. Lulin
18 Дек 2013 11:38 пп
В текущей ситуации — сложно придумать «что-то новое».
4. Alex W. Lulin
19 Дек 2013 1:50 дп
— более ЧЕМ :-)
5. Николай Зверев
19 Дек 2013 5:32 пп
О, да.. Это круто! :)
Это действительно круто, наконец-то от требований к тестам всего один шаг — формализовать требования в виде сценария… (ну и со стороны программы предоставить API)
Спасибо
6. Alex W. Lulin
19 Дек 2013 8:28 пп
«всего один шаг»
:-) добавлю «ложку дёгтя»
«ну и со стороны программы предоставить API»
— вот тут ИМХО и «кроется дьявол».
Ну я вчера Роману об этом писал.
7. Роман Янковский
19 Дек 2013 10:12 пп
Это не ложка дёгтя, это просто работа, которую нужно сделать. Волшебства не бывает.
8. Alex W. Lulin
19 Дек 2013 10:44 пп
«Это не ложка дёгтя, это просто работа, которую нужно сделать. Волшебства не бывает.»
Ясное дело :-) Я сейчас напишу в блоге свой взгляд — как я бы это делал на своей скриптовой машине.
9. Alex W. Lulin
20 Дек 2013 1:34 дп
Вот — http://programmingmindstream.blogspot.ru/2013/12/delphispec_20.html
10. Alex W. Lulin
21 Дек 2013 12:21 дп
Продолжение — http://programmingmindstream.blogspot.ru/2013/12/delphispec-2.html
11. Alex W. Lulin
23 Дек 2013 11:50 пп
Случилось мне тут «откровение» (шучу) — http://programmingmindstream.blogspot.ru/2013/12/bdd-delphispec-co.html
12. Алексей Тимохин
26 Дек 2013 7:44 дп
Класс!
Я совсем недавно узнал про Rspec + Cucumber, раздобыл себе книжку
от автора Rspec-a «The RSpec Book: Behaviour Driven Development with Rspec, Cucumber, and Friends (Facets of Ruby)», начал читать — и первые мысли были «ох и красиво ж получается, вот бы такое в Delphi».
p.s. книжку, кстати, рекомендую (она не про rspec, она про мышление).
13. Alex W. Lulin
26 Дек 2013 11:34 пп
Ром, повторюсь. Круто!
РЕАЛЬНО круто. Особенно «контекст» и «таблицы». Впечатляет!
Я правда остаюсь при мнении, что «мои скрипты круче и гибче» и на них можно сделать всё то же самое.
Но ключевое слово — «можно», а не «сделано».
Но ты — РЕАЛЬНО «попал в жилу» и смог людям объяснить, что «им это нужно» :-)
Респект :-)
Особенно «контекстам».
14. Роман Янковский
27 Дек 2013 12:28 дп
Спасибо, парни, огромное за поддержку! :)
Думаю, я позже отдельным постом опишу все возможности, которые со времени начальной публикации появились.