Ещё раз про DelphiSpec (http://roman.yankovsky.me/?p=1258) и "тестировании по-русски" (http://18delphi.blogspot.ru/2013/11/gui.html).
Побудительным мотивом данного поста послужил комментарий Николая Зверева - "Это действительно круто, наконец-то от требований к тестам всего один шаг — формализовать требования в виде сценария… (ну и со стороны программы предоставить API)".
Тут я понял, что "я не всё правильно рассказал про "свои скрипты"" и что "Роман уделал меня на моём же поле" :-)
Я не претендую на первенство или оригинальность.
Тем более я не хочу умалять достоинства разработки Романа или критиковать её.
Я лишь хочу подкинуть "пищу для размышлений".
Итак - "как я бы делал бы "то же самое" на своей скриптовой машине".
Давайте возьмём пример Романа:
И "адаптируем" его к нашей скриптовой машине.
Первым делом посмотрим на класс:
Отобразим его (на стороне Delphi) на аксиоматику тестовой машины.
Это можно сделать несколькими способами:
1. Ручное отображение, через RegisterMethod.
2. UML и кодогенерацию.
3. "Новый" RTTI.
Я пользуюсь ВСЕМИ этими способами, в зависимости от задачи и её сложности.
Но это в общем - "не сильно важно".
Я лишь хочу наметить "пути".
Если кому-то интересен код "отображения" - я им потом поделюсь.
В результате отображения получим следующие слова скриптовой машины:
Теперь опишем аксиоматику на стороне скриптов:
Отдельно - базовую аксиоматику (она - ОДНА "на всех", это можно ОДИН РАЗ написать и "забыть как страшный сон"):
Далее регистрируем слова-"заглушки", семантика которых не влияет на код сценария.
Если я всё правильно понял - они служат лишь "декорацией".
(Роман наверное меня поправит, если я что-то не так понял)
И ещё определяем слово Then.
Семантика которого понятна - проверить условие и вывести ошибку, если условие не выполняется.
И отдельно аксиоматику калькулятора:
Тогда пример переписывается вот так:
Выглядит наверное "тяжеловесно" для начала.
Но!
Это практически повторяет реализацию Романа, но при этом обладает - гораздо большей гибкостью.
Это - раз.
Два - это то, что код скриптов не требует перекомпиляции приложения.
А три - это то, что классы в "аксиоматику" можно отображать "один в один" (разными способами, я часть из них перечислил выше), а "синтаксический сахар" - можно "добирать" уже средствами скриптов.
А четыре - скрипты - КОМПИЛИРУЮТСЯ, ДО их выполнения. С указанием места ошибки. В отличии от регулярных выражений.
Повод для размышлений?
Побудительным мотивом данного поста послужил комментарий Николая Зверева - "Это действительно круто, наконец-то от требований к тестам всего один шаг — формализовать требования в виде сценария… (ну и со стороны программы предоставить API)".
Тут я понял, что "я не всё правильно рассказал про "свои скрипты"" и что "Роман уделал меня на моём же поле" :-)
Я не претендую на первенство или оригинальность.
Тем более я не хочу умалять достоинства разработки Романа или критиковать её.
Я лишь хочу подкинуть "пищу для размышлений".
Итак - "как я бы делал бы "то же самое" на своей скриптовой машине".
Давайте возьмём пример Романа:
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
И "адаптируем" его к нашей скриптовой машине.
Первым делом посмотрим на класс:
type
TCalculator = class
private
FData: TStack<integer>;
FValue: Integer;
public
constructor Create;
destructor Destroy; override;
procedure Add;
procedure Mul;
procedure Push(Value: Integer);
property Value: Integer read FValue;
end;
Отобразим его (на стороне Delphi) на аксиоматику тестовой машины.
Это можно сделать несколькими способами:
1. Ручное отображение, через RegisterMethod.
2. UML и кодогенерацию.
3. "Новый" RTTI.
Я пользуюсь ВСЕМИ этими способами, в зависимости от задачи и её сложности.
Но это в общем - "не сильно важно".
Я лишь хочу наметить "пути".
Если кому-то интересен код "отображения" - я им потом поделюсь.
В результате отображения получим следующие слова скриптовой машины:
OBJECT: TCalculator FUNCTION TCalculator.Create PROCEDURE TCalculator.Add OBJECT: TCalculator IN aCalculator PROCEDURE TCalculator.Mul OBJECT: TCalculator IN aCalculator PROCEDURE TCalculator.Push INTEGER IN aValue OBJECT: TCalculator IN aCalculator INTEGER FUNCTION TCalculator.GetValue OBJECT: TCalculator IN aCalculator
Теперь опишем аксиоматику на стороне скриптов:
Отдельно - базовую аксиоматику (она - ОДНА "на всех", это можно ОДИН РАЗ написать и "забыть как страшный сон"):
[WordWorker2]
// - [WordWorker2] это слово, которое выполняется при компиляции кода и принимает ДВА ПАРАМЕТРА СПРАВА
Feature:
FUNCTOR IN aClassFactory
// - aClassFactory - фабрика тестируемого класса
aClassFactory := WordToWork1
[] VAR l_Steps
// - объявляем массив сценариев
l_Steps := ( [[ WordToWork2 DO ]] )
// - складываем сценарии в массив
@
(
FUNCTOR IN aStep
// - aStep - текущий сценарий
aClassFactory aStep DO
) ITERATE l_Steps
// - регистрируем сценарии в тестовой машине
; // Feature:
OBJECT VAR gTestedObject
// - текущий тестируемый объект
WordWorker2 Scenario: FUNCTOR IN anObjectConstructor
STRING VAR l_ScenarioName
// - объявляем переменную для имени сценария
l_ScenarioName := ( WordToWork1 DO )
// - получаем имя сценария
l_ScenarioName
// - кладём имя сценария на стек
@ (
gTestedObject := (
[
// - Обратно переключаемся в режим выполнения
anObjectConstructor CompileValue
// - компилируем значение anObjectConstructor как "литерал" в коде
]
// - Обратно переключаемся в режим компиляции
DO
)
// - создаём тестируемый объект
TRY
[ WordToWork2 CompileValue ] DO
// - выполняем код сценария
FINALLY
gTestedObject TObject.Free
// - уничтожаем тестируемый объект
END
)
// - компилируем код сценария на стек
TestEngine.RegisterTest
// - регистрируем в тестовой машине тест с именем сценария и указанным кодом
; // Scenario:
Далее регистрируем слова-"заглушки", семантика которых не влияет на код сценария.
Если я всё правильно понял - они служат лишь "декорацией".
(Роман наверное меня поправит, если я что-то не так понял)
WordWorker Given WordtoWork DO ; // Given WordWorker And WordtoWork DO ; // And WordWorker When WordtoWork DO ; // When
И ещё определяем слово Then.
Семантика которого понятна - проверить условие и вывести ошибку, если условие не выполняется.
WordWorker Then // - ожидает, что параметр справа вычислит массив из ДВУХ значений - булевского и строкового [] VAR l_Check // - объявляем массив l_Check := ( WordtoWork DO ) // - присваиваем массиву значение BOOLEAN VAR l_Condition // - условие l_Condition := ( l_Check [0] ) if ( NOT l_Condition ) then ( Fail ( l_Check [1] ) // - выводим сообщение о неуспехе ) ; // Then
И отдельно аксиоматику калькулятора:
OBJECT FUNCTON Calculator
Resut := ( @ TCalculator.Create )
// - возвращаем указатель на конструктор объекта
;
PROCEDURE "I have entered {(INTEGER IN aValue)} in calculator"
aValue gTestedObject TCalculator.Push
;
PROCEDURE "I press Add"
gTestedObject TCalculator.Add
;
PROCEDURE "I press mul"
gTestedObject TCalculator.Mul
;
[] FUNCTION "the result should be {(INTEGER IN aValue)} on the screen"
Result :=
[[
( gTestedObject TCalculator.GetValue = aValue )
'Incorrect result on calculator screen'
]]
;
Тогда пример переписывается вот так:
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"
)
)
Выглядит наверное "тяжеловесно" для начала.
Но!
Это практически повторяет реализацию Романа, но при этом обладает - гораздо большей гибкостью.
Это - раз.
Два - это то, что код скриптов не требует перекомпиляции приложения.
А три - это то, что классы в "аксиоматику" можно отображать "один в один" (разными способами, я часть из них перечислил выше), а "синтаксический сахар" - можно "добирать" уже средствами скриптов.
А четыре - скрипты - КОМПИЛИРУЮТСЯ, ДО их выполнения. С указанием места ошибки. В отличии от регулярных выражений.
Повод для размышлений?
P.S. Продолжение темы - http://programmingmindstream.blogspot.ru/2013/12/delphispec-2.html
И это тоже круто :)
ОтветитьУдалитьВообще идея тестирования, на базе неких user-friendly скриптов (а не, так сказать, hard-code) - круто. Очень-мега-круто.
Потому что тестирование и программирование - это всё-таки разные вещи. Конечно, есть люди, которые могут в себе сочетать несколько направлений деятельности - и БД проектировать, и API к этой БД, и web, использующий этот API, и Delphi-клиент, использующий это API, и ещё тесты (и того, и другого и третьего)... но это и редкость, и не есть хорошо в принципе.
Сегодня я больше склоняюсь к принципу "разделяй и властвуй". Т.е.: "ты пишешь код (который предоставляет описанное API), а ты пишешь скрипт (который тестирует этот код по описанному API)". ..как-то так..
"Потому что тестирование и программирование - это всё-таки разные вещи."
УдалитьТак я вроде и написал про "слои"...
А если, при этом, код и тесты формируются автоматически по модели.. эхъ.
ОтветитьУдалить"А если, при этом, код и тесты формируются автоматически по модели.. эхъ."
УдалитьТак и есть :-) И ТЗ кстати - тоже...
Интересно, но по-моему более трудоемко, чем использование DelphiSpec (забудем, что она не доделана еще, поговорим абстрактно)... хотя безусловно гибче. Вопрос: а нужна ли такая гибкость? Цель Gherkin - избавить людей от программирования, а избавление от программирования и большая гибкость плохо сочетаются.
ОтветитьУдалитьНо интересная вещица, безусловно. Я, признаюсь, только теперь кажется окончательно понял, как оно у тебя устроено :)
"Я, признаюсь, только теперь кажется окончательно понял, как оно у тебя устроено :)"
УдалитьНу слава богу :-) не прошло и года - как я смог кому-нибудь что-нибудь объяснить :-) я тот ещё "объяснятель"..
я знал что ты про трудоёмкость скажешь :-)
ОтветитьУдалитьповерь - тебе только так КАЖЕТСЯ
" а избавление от программирования и большая гибкость плохо сочетаются."
ОтветитьУдалитьВроде всё сочетается. Я же написал, что есть "слои". проектные классы -> API - > обвязка -> Собственно скрипты