В блоге я уже несколько раз писал о предметно-ориентированных языках (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.




