Ещё раз про 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 - > обвязка -> Собственно скрипты