пятница, 29 ноября 2013 г.

GUI-тестирование "по-русски". Прикручиваем DUnit к нашим "скриптам"

GUI-тестирование "по-русски". Прикручиваем DUnit к нашим "скриптам"

Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/gui-back-to-basics_22.html

Теперь давайте сделаем вот какую штуку - прикрутим DUnit к нашим скриптам.

Для начала сделаем тестовый VCL проект по аналогии с FM проектом.

Offtopic. Небольшое пояснение о блоге

Небольшое пояснение о блоге.

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

Публиковать тут я буду РОВНО раз в неделю.

Или 0 раз в неделю. Если ничего достойного не напишется.

А "поток сознания" переехал сюда - http://programmingmindstream.blogspot.com/

Для "особых ценителей".

Там я в перепалки - НЕ ВСТУПАЮ. Если кому-то что-то не нравится - заведите СВОЙ блог и пишите ТУДА.

СПАСИБО.

пятница, 22 ноября 2013 г.

GUI-тестирование "по-русски". Back to the basics. Пример нажатия на кнопку формы из скрипта

Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/5.html

Мне тут написали, что мол "вы пишете в формате лекции, а хотелось бы в формате семинара".

Попробую в формате семинара.

Итак.

Тезисы:

1. тест должен быть линейным
2. похожим на тест-кейс
3. читаться человеком
4. оперировать терминами предметной области

вторник, 19 ноября 2013 г.

Ещё взгляд на тестирование

У меня есть чужой материал, который для меня лично - ценен.

Хочу его опубликовать.

Итак.

Трофимов Андрей пишет:

В статье http://18delphi.blogspot.ru/2013/04/blog-post_30.html упоминается использование макросов.

Они - достаточно удобный инструмент для создания условий тестовой проверки программы, в которой используются.

Можно даже пойти дальше, и сравнить несколько примеров скриптов, проверяющих работу редактора.

В думах о тестировании №5

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/4.html

Теперь хочется пояснить "что там написано" и зачем всё это".

суббота, 16 ноября 2013 г.

В думах о тестировании №4

Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/3.html

И ещё немного попрограммировал и родилось вот что:

В думах о тестировании №3

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/2.html

И я ещё немного попрограмировал и получил такой результат:

В думах о тестировании №2

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/blog-post_15.html

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

пятница, 15 ноября 2013 г.

Чужая ссылка. Оконные сообщения в FireMonkey

http://teran.karelia.pro/articles/item_6148.html

Ну не знаю...

"Мотивации" - не вижу. А про то "как сделать"- можно и в исходниках FM почитать.

Конструкции:
except
end;

- особенно меня всегда - "радуют".

В думах о тестировании

Я уже писал о контрольных точках - http://18delphi.blogspot.ru/2013/04/blog-post_6244.html

Я достаточно широко применяю этот подход.

Я внедряю тестовый код в реальные проектные файлы.

GUI-тестирование "по русски". Back to the basics

Мне тут написали, что мол "вы пишете в формате лекции, а хотелось бы в формате семинара".

Попробую в формате семинара. (Не закончено)

Коротко. Ещё о языках программирования и читабельности. Ещё

Какой код лучше?

Этот:
 stdout << X << Y << Z

Или этот:
 stdout.Append(X).Append(Y).Append(Z)

среда, 13 ноября 2013 г.

Коротко. Ещё о языках программирования и читабельности

Был код:
           if l_W.IsImmediate AND
              (not Self.SupressNextImmediate OR l_W.IsAnonimous)
              {OR (l_Ctx.rImmediateLevel > 0)} then
            l_W.DoIt(l_Ctx)
           else
           begin
            TkwCompiledWord(l_Ctx.rWordCompilingNow).DoAddCodePart(l_W, false, l_Ctx);
            AfterCodePartAdded(l_W, l_Ctx);
           end;//l_W.IsImmediate..

Но потом функция Self.SupressNextImmediate стала возвращать ТРИ значения - No, Yes, Always.;

Тогда я этот код предпочёл переписать так:

Коротко. О языках программирования...

Я уже писал...

Вот тут например - http://18delphi.blogspot.ru/2013/07/blog-post_2853.html

Мне РЕАЛЬНО ВСЁ равно - на каком языке писать...

Коротко. В плане "ворчания"... Ссылка

http://18delphi.blogspot.ru/2013/07/blog-post_20.html?showComment=1374683823787#c2272887012991648723

Не зря мне посоветовали "читать про Cobol".

Да - "ПОХОЖИ" мои тесты на Cobol. Ну и что? При всём моём уважении к Дейкстре - у меня есть мнение, что и в 53-м году люди "правильно многое думали".

Коротко. В стиле "потока сознания"... Мне нравится "возможность иметь duck-typing", но не его "пихание во все щели"...

Мне нравится "возможность иметь duck-typing", но не его "пихание во все щели"...
"МНЕ КАЖЕТСЯ БОЛЬШОЙ ОШИБКОЙ - строить АРХИТЕКТУРУ на Duck-Typing"
решать - "частные" задачи - да.. а вот АРХИТЕКТУРУ - перебор
хотя тот же Objective-C - именно так и построен
Но! МНЕ ЛИЧНО - это не нравится....

Ещё РАЗ - АРХИТЕКТУРУ на основе Duck-Typing - я бы лично - не стал бы строить...

Не знаю почему..

"Нутром чую"...

Хотя тот же Apple - БОЛЕЕ ЧЕМ - удачный пример.

О технарях и "математиках"...

Скажем так по мотивам вот этого - http://18delphi.blogspot.ru/2013/11/blog-post_2.html

И немного вот этого - http://18delphi.blogspot.ru/2013/11/supports.html

Наверное я ещё раз напишу ГЛУПОСТЬ, но похоже - я без этого НЕ МОГУ...

Есть "технари", инженЕры или инженерА... Как кому будет угодно...

И есть - "математики"...

воскресенье, 10 ноября 2013 г.

Чужая ссылка. "Побег от Зурга"

http://geniepro.livejournal.com/2840.html#cutid1

Прикольно. Только вот - "Идея программы на Прологе заключается в представлении промежуточных состояний переходов через мост фактами вида st(P,L), где L - список игрушек, находящихся в данный момент на левой стороне моста, а P - признак, показывающий положение фонарика (левая или правая сторона)"

Зачем искусственно "криптовать" код вводя "однобуквеные" имена переменных - никогда не понимал. И видимо - уже пойму.

суббота, 9 ноября 2013 г.

GUI-тестирование "по-русски". Оператор @ №2

Поскольку оператор @ - это "слово аксиоматики", то регистрируется оно "на стороне Delphi".

А как?

Ещё вопрос к аудитории...

Учитывая вот это -  http://18delphi.blogspot.com/2013/11/gui.html?showComment=1383817747607#c2293914289347783933

Если у кого-нибудь есть примеры РЕАЛЬНОЙ надобности тестирования БД-приложений - присылайте, не стесняйтесь.

Мне было бы - ОЧЕНЬ интересно - "родить" тестовые примеры, ИМЕННО для этой области.

Синергия "тестируемости" и "хорошего дизайна"

По мотивам - http://18delphi.blogspot.ru/2013/04/blog-post_3844.html

Смотрите какая штука.

Чтобы "код бизнес-логики" смог вызвать тестировщик, который "в общем случае" - НЕ ПРОГРАММИСТ. То этот код должен быть написан ТАК, чтобы и "домохозяйка поняла". Без "всякого хоккея".

Ещё человек написал...

XXX: круть

lulinalex: что?

XXX: да вообще вся серия про тестирование по русски. правда особо пространствить руки не доходят да и без модели=dsl с кодогенератором оно сложноприменимо

GUI-тестирование "по-русски". Маленькая ремарка. "Где это всё живёт"

Реально - "забросали вопросами". Примерно - ОДИНАКОВЫМИ.

Где это всё живёт?

"Внутри приложения" или СНАРУЖИ?

GUI-тестирование "по-русски". Работа с "контролом в фокусе" и определение его класса

Отвлечёмся немного от "хардкорного программирования" и устройства скриптовой машины.

Вот тут - http://18delphi.blogspot.ru/2013/11/gui.html - я писал как искать форму и контрол по их имени

А вот тут - http://18delphi.blogspot.ru/2013/11/gui_4.html - я писал - как в TEdit ввести текст.

А вот тут - http://18delphi.blogspot.ru/2013/11/gui-rtti.html - я писал как сделать "то же самое" средствами "классического" RTTI.

Напомню, что у нас в итоге получилось:

PROCEDURE "Присвоить текст в текущий контрол" STRING IN aText
 OBJECT VAR l_Control
 FocusedControl =: l_Control
 aText l_Control EditSetText
END //"Присвоить текст в текущий контрол"

Теперь пусть мы опять же хотим поработать с контролом в фокусе, но при этом мы хотим проконтролировать, что контрол в фокусе - НУЖНОГО нам класса - TEdit или TCustomEdit, ну или наследник он него.

Ну скажем - логика теста зависит от этого.

Как это сделать?

Сейчас расскажу.

Напишем для начала два "вспомогательных" слова. 

Они - в ПРИНЦИПЕ полезны для GUI-Тестирования.

Но и ещё они помогут нам расширить наши примеры.

Вот эти слова:

OBJECT FUNCTION "Контрол в фокусе"
 FocusedControl =: Result
END // "Контрол в фокусе"

WORDWORKER "Присвоить текст в" STRING IN aText
 OBJECT VAR l_Control
 WordToWork DO =: l_Control
 aText l_Control EditSetText
END // "Присвоить текст в"

Про слово WORDWORKER я писал тут - http://18delphi.blogspot.ru/2013/11/gui_9015.html и тут - http://18delphi.blogspot.ru/2013/11/gui-2_7.html

Тогда наш пример переписывается так:

PROCEDURE "Присвоить текст в текущий контрол" STRING IN aText
 "Присвоить текст  {(aText)} в" "Контрол в фокусе"
END //"Присвоить текст в текущий контрол"

Конкатенативный язык (http://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D0%BA%D0%B0%D1%82%D0%B5%D0%BD%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F).

Не правда ли?

Теперь КАК определить, что "Контрол в фокусе" - НУЖНОГО нам класса?

А вот как:

На стороне Delphi определяем слово Inherits:

interface

type
 TkwInherits = class(TscriptKeyWord)
  protected
   procedure DoIt(aContext: TscriptContext); override;
 end;//TkwInherits

implementation

procedure TkwInherits.DoIt(aContext: TscriptContext);
var
 l_ClassName : String;

function localIsInherits(anObjectClass : TClass): Boolean;
begin
 if (anObjectClass = nil) then
  Result := false
 else
 if SameText(anObjectClass.ClassName, l_ClassName) then
  Result := true
 else
  Result := localIsInherits(anObjectClass.ClassParent);
end;
 
var
 l_Object : TObject;
begin
 l_Object := aContext.PopObject;
 Assert(l_Object <> nil);
 l_ClassName := aContext.PopString;
 aContext.PushBoolean(localIsInherits(l_Object.ClassType));
end;

initialization
 ScriptEngine.RegisterKeyWord(TkwInherits, 'Inherits');
 // - далее, если вызов RegisterKeyWord будет в виде RegisterKeyWord(TkwXXX, 'XXX'), 
 //   то я буду просто так и писать RegisterKeyWord(TkwXXX).
 //
 //   Тем более, что это у меня так и реализовано, через использование ClassName

Тогда можно написать такой код скрипта:

 'TCustomEdit' "Контрол в фокусе" Inherits IF (
  "Присвоить текст  {(aText)} в" "Контрол в фокусе"
 )

Ну или такой:

 'TCustomEdit' "Контрол в фокусе" Inherits ASSERT
 "Присвоить текст  {(aText)} в" "Контрол в фокусе"

Поработаем ещё немного.

Введём в словарь определение вот такого слова:

CONST "Строка ввода" 'TCustomEdit'

Тогда можно написать так:

 "Строка ввода" "Контрол в фокусе" Inherits IF (
  "Присвоить текст  {(aText)} в" "Контрол в фокусе"
 )

Ещё несколько штрихов.

Для начала определим слово ЯВЛЯЕТСЯ:

BOOLEAN WORDWORKERFUNCTION ЯВЛЯЕТСЯ OBJECT IN anObject
 WordToWork DO 
 // - вычисляем "параметр справа"
 anObject 
 // - anObject - "параметр слева"
 Inherits 
 // - зовём уже знакомое нам слово Inherits
 =: Result
 // - результат нашиз вычислений помещаем в результат функциии ЯВЛЯЕТСЯ
END // ЯВЛЯЕТСЯ

Тогда можно написать так:

 
 "Контрол в фокусе" ЯВЛЯЕТСЯ "Строка ввода" IF (
  "Присвоить текст  {(aText)} в" "Контрол в фокусе"
 )

Ещё определим синоним:

WordAlias "Строкой ввода" "Строка ввода"

Тогда можно написать так:

 
 "Контрол в фокусе" ЯВЛЯЕТСЯ "Строкой ввода" IF (
  "Присвоить текст  {(aText)} в" "Контрол в фокусе"
 )

Ещё немного поработаем над IF.

У нас помимо слова WORDWORKER - есть слово WORDWORKER2.

Которое умеет работать с двумя "параметрами справа".

Применим его.

Напишем следующее:

WORDWORKER2 если
 WordToWork1 DO
 // - вычислили значение "первого аргумента справа"
 IF (
  WordToWork2
  DO
  // - выполняем "второй аргумент справа"
 )
END // если

И тогда наш пример можно переписать так:

 если ( "Контрол в фокусе" ЯВЛЯЕТСЯ "Строкой ввода")
  ( "Присвоить текст  {(aText)} в" "Контрол в фокусе" )

По-русски? По-моему - по русски?

Теперь если определить слово "то":

Так:

WordAlias то NOP
// - NOP - это слово, которое ничего не делает, 
//   но может появляться там, где грамматика требует слово

Или так:

WORDWORKER то
 WordToWork DO
 // - просто вычисляем "аргумент справа"
 //   т.е. слово "то" в данном случае является proxy-словом
END // то

То мы можем переписать пример так:

если ( "Контрол в фокусе" ЯВЛЯЕТСЯ "Строкой ввода") то
  ( "Присвоить текст  {(aText)} в" "Контрол в фокусе" )

Ну и ещё введём синоним:

WordAlias Если если
// - у нас скриптовая машина - РЕГИСТРОЗАВИСИМАЯ, это - ОСОЗНАННО

Тогда можно написать так:

 Если ( "Контрол в фокусе" ЯВЛЯЕТСЯ "Строкой ввода") то
  ( "Присвоить текст  {(aText)} в" "Контрол в фокусе" )

Ну и "на закуску".

Ещё есть слово WORDWORKER2+.

Оно умеет работать с двумя фиксированными "параметрами справа".

И одним опциональным. Предварённым другим ключевым словом.

Вот пример:

WORDWORKER2+ 
 если 
  // - "если" - ОСНОВНОЕ ключевое слово. С которого начинается разбор грамматики
 иначе
 // - "иначе" - ДОПОЛНИТЕЛЬНОЕ ключевое слово. Которого может и не быть.
 WordToWork1 DO
 // - вычислили значение "первого аргумента справа"
 IF (
  WordToWork2
  DO
  // - выполняем "второй аргумент справа"
 )
 ELSE (
  WordToWork2+
  DO
  // - выполняем третий опциональный аргумент справа, если он передан"
  //   Если же он НЕ ПЕРЕДАН, то нам в аргументе WordToWork2+ - передадут уже знакомое слово NOP
 )
END // если

Тогда можно написать так:

 Если ( "Контрол в фокусе" ЯВЛЯЕТСЯ "Строкой ввода") то
  ( "Присвоить текст  {(aText)} в" "Контрол в фокусе" )
 иначе
  ( false ASSERT ) // - тут просто "упадём".

По-моему - "уже перебор информации".

Поэтому - закругляюсь.

Чуть позже я расскажу - как регистрировать РЕАЛЬНЫЕ классы объектов в тестовой машине, чтобы работать с их идентификаторами, а не со "строковыми именами".

И БОЛЕЕ ТОГО - вызывать их КОНСТРУКТОРЫ. Т.е. на самом деле - скриптовая машина - МОЖЕТ обладать возможностью СОЗДАВАТЬ объекты реального приложения.

Неплохо для "тестовой машины", а?

На самом деле - наверное самые пытливые читатели отметили, для себя, что эту скриптовую машину можно использовать не только для тестирования, но и для АВТОМАТИЗАЦИИ и написания макросов пользователя.

Такой вот VBA (http://ru.wikipedia.org/wiki/VBA) "на коленке".

Если компилировать "скриптовую обвязку" не только под IfDef Test, но и для конечного клиентского приложения.

И ДАТЬ пользователю возможность загружать и выполнять скрипты.

P.S. Я ещё вас не утомил? А то у меня план статей разросся уже до 30-ти штук.

Кому-то вообще интересно "сделать скриптовую машину своими руками" из "модели для сборки"?

Или пора закругляться?

Учитывая, что каждая новая статья приносит с собой ещё две статьи в "план для написания".

пятница, 8 ноября 2013 г.

Переменные мета-класса. Чужие ссылки

Вот тут - http://18delphi.blogspot.ru/2013/04/delphi.html я писал о "переменных мета-класса". И как их сделать "на коленке".

А вот Николай Зверев  прислал мне такие ссылки:
http://hallvards.blogspot.ru/2007/05/hack17-virtual-class-variables-part-i.html
http://hallvards.blogspot.ru/2007/05/hack17-virtual-class-variables-part-ii.html

Люди - думают о том же самом. Значит - не совсем - "велосипед изобретаю".

Виктор Морозов кстати - мне тоже присылал подобные ссылки. И я их уже где-то в блоге публиковал.

Не могу только найти где.

GUI-тестирование "по-русски". Продолжение про WordWorker'ы

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/gui-3-wordworker.html

Теперь как же в процедуру WorkTheWord передаётся управление и откуда берётся aWordToTheRight?

Дело в том, что слово  TscriptWordWorker - помечено, как слово "непосредственного исполнения". Т.е. оно выполняется в процессе компиляции кода.

Как это выглядит?

А вот так:

interface

type
  TscriptWordWorker = class(TscriptKeyWord)
   protected
    function IsImmediate: Boolean; override;
    procedure DoIt(aContext: TscriptContext); override;
    procedure WorkTheWord(aWordToTheRight : TscriptKeyWord; aContext: TscriptContext); virtual; abstract;
  end;//TscriptWordWorker

implementation

function TscriptWordWorker.IsImmediate: Boolean;
begin
 Result := true;
end;

type
  TscriptCompiledWordWorker = class(TscriptKeyWord)
  // - вспомогательное слово, которое компилируется по месту вызова
   private
    f_Worker : TscriptWordWorker;
    f_WordToRight : TscriptKeyWord;
   protected
    procedure DoIt(aContext: TscriptContext); override;
    procedure Cleanup; override;
   public
    constructor Create(aWorker : TscriptWordWorker; aWordToRight: TscriptKeyWord);
  end;//TscriptCompiledWordWorker

constructor TscriptCompiledWordWorker.Create(aWorker : TscriptWordWorker; aWordToRight: TscriptKeyWord);
begin
 inherited Create;
 aWorker.SetReferenceTo(f_Worker);
 aWordToRight.SetReferenceTo(f_WordToRight);
end;

procedure TscriptCompiledWordWorker.Cleanup;
begin
 FreeAndNil(f_Worker);
 FreeAndNil(f_WordToRight);
 inherited;
end;

procedure TscriptCompiledWordWorker.DoIt(aContext: TscriptContext);
begin
 f_Worker.WorkTheWord(f_WordToRight);
end;

procedure TscriptWordWorker.DoIt(aContext: TscriptContext);
var
 l_CompileContext : TscriptCompileContext; 
 l_WordToTheRight : TscriptKeyWord;
 l_CompiledWordWorker : TscriptCompiledWordWorker;
begin
 l_CompileContext := (aContext As TscriptCompileContext);
 l_WordToTheRight := l_CompileContext.CompileWordToTheRight; // - Об устройстве этого метода поговорим чуть позже
 try
  l_CompiledWordWorker := TscriptCompiledWordWorker.Create(Self, l_WordToTheRight);
  try
   l_CompileContext.Code.CompileKeyWord(l_CompiledWordWorker);
  finally
   FreeAndNil(l_CompiledWordWorker);
  end;//try..finally
 finally
  FreeAndNil(l_WordToTheRight);
 end;//try..finally
end;

Устройство метода CompileWordToTheRight - разберём в следующих сериях.

GUI-тестировании "по-русски". Как всё это устроено №3. Как устроена работа с переменными, оператор равно и что такое WordWorker'ы.

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/gui-2.html

Теперь хочется рассказать вот о чём.

Как устроена работа с переменными, оператор равно и что такое WordWorker'ы.

Рассмотрим пример:

VAR I
1 =: I

Что тут написано?
VAR I - определяет переменную с именем I в текущем контексте видимости (о контекстах позже поговорим отдельно).
1 - кладёт на стек значений целое число 1.
=: I - выбирает значение со стека и кладёт его в переменную I.

Как всё это устроено?

Сейчас попробую рассказать.

До сих пор нам встречались слова-наследники от TscriptKeyWord, которые умели выбирать значения из стека значений (TscriptContext) и работать с ними. Также они умели класть значения на стек. Пример тут - http://18delphi.blogspot.ru/2013/11/gui.html

Теперь хочется затронуть слова, которые не только умеют работать со значениями со стека ("параметры слева"), но и со словами, которые следуют в коде за ними ("параметры справа").

Одним из таких слов и является оператор равно - =:

Семантически он похож на оператор равно Паскаля, только "перевёрнут в другую сторону". Это "наследие" обратной польской записи, но это, поверьте мне, не так уж и важно.

Слова, которые умеют работать с "параметрами справа" я исторически называю WordWorker'ами.

И соответственно - наследуются они от класса TscriptWordWorker.

Разберём устройство этого класса:
interface

type
  TscriptWordWorker = class(TscriptKeyWord)
   protected
    procedure WorkTheWord(aWordToTheRight : TscriptKeyWord; aContext: TscriptContext); virtual; abstract;
  end;//TscriptWordWorker

Пока в дальнейшие детали этого слова вдаваться не будем.

Также есть ещё наследник от него TscriptVarWorker - слово, которое обрабатывает переменную.
Выглядит оно вот так:

interface

type
  IscriptVar = interface
   ['{39369047-E783-4A4B-9657-6C2450D737C3}']
   procedure PopFrom(aContext: TscriptContext);
   ...
  end;//IscriptVar

  TscriptVarWorker = class(TscriptWordWorker)
   protected
    procedure WorkTheWord(aWordToTheRight : TscriptKeyWord; aContext: TscriptContext); override;
    procedure WorkTheVar(aVarToTheRight : IscriptVar; aContext: TscriptContext); virtual; abstract;
  end;//TscriptVarWorker 

implementation

procedure TscriptVarWorker.WorkTheWord(aWordToTheRight : TscriptKeyWord; aContext: TscriptContext)
var
 l_Var : IscriptVar;
begin
 if Supports(aWordToTheRight, l_Var) then
  WorkTheVar(l_Var, aContext)
 else
  Assert(false);
end;

И тогда мы можем написать:

interface

type
  TkwPopToVar = class(TscriptVarWorker)
   protected
    procedure WorkTheVar(aVarToTheRight : IscriptVar; aContext: TscriptContext); override;
  end;//TkwPopToVar

implementation

procedure TkwPopToVar.WorkTheVar(aVarToTheRight : IscriptVar; aContext: TscriptContext);
begin
 aVarToTheRight.PopFrom(aContext);
end;

initialization
 ScriptEngine.RegisterKeyWord(TkwPopToVar, '=:');

Пока - всё.

Дальнейшие детали опишу в следующих сериях.

GUI-Тестирование "по-русски". Как всё устроено №2.5. Про TscriptContext

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/gui-2.html

Теперь хочется рассказать про устройство TscriptContext.

В целом это конечно - "стек значений".

Но не только... Но об этом - позже.

Пока - про стек..

Стек значений - это ОДИН из краеугольных камней устройства нашей тестовой машины.

Теорию можно почитать тут - http://ru.wikipedia.org/wiki/Forth

И кстати ещё одна забавная ссылка - http://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D0%BA%D0%B0%D1%82%D0%B5%D0%BD%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F

А пока - займёмся практикой.

Как выглядит стек значений внутри.
Примерно вот так:
interface

type
  TscriptValueType = (script_vtVoid, script_vtInteger, script_vtBoolean, script_vtString, script_vtObject);

  TscriptValue = {$IfDef XE}record{$Else}object{$EndIf}
   private
    rType : TscriptValueType;
    rInteger : Integer;
    rBoolean : Boolean;
    rString : String;
    rObject : TObject;
   public
    function AsInteger: Integer;
    function AsBoolean: Boolean;
    function AsString: String;
    function AsObject: TObject;

    function EQ(anOther: TscriptValue): Boolean;
  end;//TscriptValue
 
  function TscriptValue_C(aValue: Integer): TscriptValue; overload;
  function TscriptValue_C(aValue: Boolean): TscriptValue; overload;
  function TscriptValue_C(aValue: String): TscriptValue; overload;
  function TscriptValue_C(aValue: TObject): TscriptValue; overload;

  // - преобразовать это в "вариантную запись" - оставляю самым пытливым читателям

  _ItemType_ = TscriptValue;
  _List_Parent_ = TRefcounted;

  {$Include _List_.imp.pas}
 
  TscriptContext = class(_List_)
   public
    function PopInteger: Integer;
    function PopBoolean: Boolean;
    function PopString: String;
    function PopObject: TObject;

    procedure PushInteger(aValue: Integer);
    procedure PushBoolean(aValue: Boolean);
    procedure PushString(aValue: String);
    procedure PushObject(aValue: TObject);
  end;//TscriptContext

implementation

function IsSame(const A: _ItemType_;
  const B: _ItemType_): Boolean;
begin
 Result := A.EQ(B);
end;//IsSame

procedure FreeItem(var thePlace: _ItemType_);
begin
 Finalize(thePlace);
end;//FreeItem
 
procedure FillItem(var thePlace: _ItemType_;
  const aFrom: _ItemType_);
begin
 thePlace := aFrom;
end;//FillItem

{$Include _List_.imp.pas}

function TscriptValue.AsInteger: Integer;
begin
 Assert(rType = script_vtInteger);
 Result := rInteger;
end;

function TscriptValue.AsBoolean: Boolean;
begin
 Assert(rType = script_vtBoolean);
 Result := rBoolean;
end;

function TscriptValue.AsString: String;
begin
 Assert(rType = script_vtString);
 Result := rInteger;
end;

function TscriptValue.AsObject: TObject;
begin
 Assert(rType = script_vtObject);
 Result := rObject;
end;

function TscriptValue.EQ(anOther: TscriptValue): Boolean;
begin
 Result := (rType = anOther.rType);
 if Result then
 begin
  Case rType of
   script_vtInteger:
    Result := Self.AsInteger = anOther.AsInteger;
   script_vtBoolean:
    Result := Self.AsBoolean = anOther.AsBoolean;
   script_vtString:
    Result := Self.AsString = anOther.AsString;
   script_vtObject:
    Result := Self.AsObject = anOther.AsObject;
   else
   begin
    Assert(false);
    Result := false;
   end;//else
  end;//Case rType
 end;//Result
end;

function TscriptValue_C(aValue: Integer): TscriptValue; 
begin
 Result.rType := script_vtInteger;
 Result.rInteger := aValue;
end;

function TscriptValue_C(aValue: Boolean): TscriptValue;
begin
 Result.rType := script_vtBoolean;
 Result.rBoolean := aValue;
end;

function TscriptValue_C(aValue: String): TscriptValue;
begin
 Result.rType := script_vtString;
 Result.rString := aValue;
end;
 
function TscriptValue_C(aValue: TObject): TscriptValue;
begin
 Result.rType := script_vtObject;
 Result.rObject := aValue;
end;

function TscriptContext.PopInteger: Integer;
begin
 Result := Self.Last.AsInteger;
 Delete(Pred(Count));
end;

function TscriptContext.PopBoolean: Boolean;
begin
 Result := Self.Last.AsBoolean;
 Delete(Pred(Count));
end;

function TscriptContext.PopString: String;
begin
 Result := Self.Last.AsInteger;
 Delete(Pred(Count));
end;

function TscriptContext.PopObject: TObject;
begin
 Result := Self.Last.AsObject;
 Delete(Pred(Count));
end;

procedure TscriptContext.PushInteger(aValue: Integer);
begin
 Self.Add(TscriptValue_C(aValue));
end;

procedure TscriptContext.PushBoolean(aValue: Boolean);
begin
 Self.Add(TscriptValue_C(aValue));
end;

procedure TscriptContext.PushString(aValue: String);
begin
 Self.Add(TscriptValue_C(aValue));
end;

procedure TscriptContext.PushObject(aValue: TObject);
begin
 Self.Add(TscriptValue_C(aValue));
end;

Вот как-то так пока...

Замечу лишь, что ещё есть наследник от TscriptContext - TscriptCompileContext:

  TscriptCompileContext = class(TscriptContext)
   property Parser: IscriptParser;
    {* - Текущий парсер. }
   property Code: TscriptCode;
    {* - Текущий компилируемый код. }
  end;//TscriptCompileContext

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

О нём подробнее я напишу в следующих сериях.

четверг, 7 ноября 2013 г.

Вопрос про "GUI-тестирование "по-русски""

Поскольку тема - ОБШИРНАЯ.

И если она кому-нибудь интересна, то хочется узнать.

В ПЕРВУЮ очередь писать о чём?

О том "как это устроено внутри"? Или концептуально как это делать? Например "как писать тесты в терминах предметной области".

Мне-то как программисту конечно интереснее писать - "как это устроено внутри".

Товарищ написал. "К слову про синтаксический сахар"

К этому http://delphiway.ru/enumerators-%D0%B2-delphi-%D1%87%D0%B0%D1%81%D1%82%D1%8C-2/

 Еще можно объявить enumerator для for-in не как class, а как record с нужными методами и поэкономить "на спичках" за счет выделения память под этот enumerator (ибо компилятор генерит try..finally, в котором enumerator, объявленный как класс создается, с ним что-то делается, а в конце - ему зовется Destroy):

type
  TSomeListEnumerator = record
  private
   f_CurrentIndex: Integer;
   f_List: TSomeList;
   function GetCurrent: TSomeListItem;
  public
   constructor Create(AList: TSomeList);
   function MoveNext: Boolean;
   property Current: TSomeListItem;
  end;

К нему же можно добавить class operator Increment, class operator Decrement и class operator Equal / NotEqual и тогда можно так:

l_Enum := l_List.GetEnumerator;

//в сторону к концу
while (l_Enum <> l_List.EndEnum) do
begin
  ...
  Inc(l_Enum);
end;

//обратно
while (l_Enum <> l_List.StartEnum) do
begin
  ...
  Dec(l_Enum);
end;

Offtopic. Товарищ прислал. В тему в общем-то

"Даня написал клёвый пост. Если подставишь вместо "комиксист" - "программист", а вместо "комикс" - "программу", то, наверное, узнаешь себя: http://blog.dahr.ru/?p=5950 "

ОСТОРОЖНО - нецензурная лексика!

Offtopic. Прислали отзыв. Насчёт "GUI-тестирования "по-русски""

Вот он:

"Лучше, чем в любом учебнике по программированию! 

Мне очень нравится такой стиль.

http://18delphi.blogspot.com/2013/11/gui_9015.html"

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

GUI-тестирование "по-русски". Про контроль стека и числа переданных параметров

Вот тут - http://18delphi.blogspot.ru/2013/11/gui-3.html я писал про слово "между".

А что делать если кто-то захочет вызвать его вот так:

2 между ( 2 5 10 )

?

Компилятор это - спокойно "переварит", но код будет выполняться с ошибками.

Да ещё и на стеке значений будут накапливаться "паразитные значения".

Как этого избежать?

На этот случай у нас есть "специальные" слова "для контроля за стеком значений".

Слова :

#BeginControlStack - начинает контроль стека (рекурсивно)
#EndControlStack - заканчивает контроль стека (рекурсивно) и проверяет, что верхушка стека сдвинулась на выражение указанное в лямбде.

Как это применяется?

А вот так:

BOOLEAN WORDWORKERFUNCTION "строго между" INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"

 #BeginControlStack // - начинаем контроль стека
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 #EndControlStack ( 2 == ) // - заканчиваем контроль стека и убеждаемся, что на стек попало РОВНО 2 значения

 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX больше "Нижняя граница" ) И ( anX меньше "Верхняя граница" ) =: Result
END // "строго между"

Тогда код:

2 между ( 2 5 )

- нормально выполнится.

 А код:

2 между ( 2 5 10 )

- поднимет ошибку.

На самом деле есть техника проверки количества передаваемых параметров при компиляции скриптов.

Но это - отдельная тема.

Об этом я ОБЯЗАТЕЛЬНО напишу - но чуть позже.

GUI-тестирование "по-русски". Скажем так. "Анонс существующих возможностей" №3

Вот тут - http://18delphi.blogspot.ru/2013/11/gui-2_7.html я писал про WORDWORKER, WORDWORKERFUNCTION и слова "между" и "строго между".

Но я ещё обещал рассказать про логические операторы.

Сейчас расскажу.

Итак.

У нас было слово "между".

Выглядело оно вот так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" >= IF (
  X "Нижняя граница" <= IF
   true =: Result
  ELSE
   false =: Result
 )
 ELSE
  false =: Result
END //между

Два вложенных IF. Нормально.

Но как же логические операторы?

Они у нас есть.

На "хардкорном" уровне их использование выглядит так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" >= 
 anX "Верхняя граница" <= && IF
  true =: Result
 ELSE
  false =: Result
END //между

От одного IF - избавились.

Но по-моему - всё равно - "ужасно".

Обратная польская запись.

Будем исправлять ситуацию.

Введём слово:

BOOLEAN WORDWORKERFUNCTION И BOOLEAN IN aLeft
 aLeft IF
  WordToWork DO IF
   true =: Result
  ELSE
   false =: Result
 ELSE
  false =: Result
END // И

Мы ввели оператор "И" - мало того, что он ИНФИКСНЫЙ, так он ещё и ЛЕНИВЫЙ.

Т.е. он может не вычислять ВСЁ ВЫРАЖЕНИЕ, если уже понятен результат.

Тогда наш пример переписывается так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX "Нижняя граница" >= ) И ( anX "Верхняя граница" <= ) IF
  true =: Result
 ELSE
  false =: Result
END //между

Поработаем ещё.

Введём слова:

BOOLEAN WORDWORKERFUNCTION "меньше или равно" INTEGER IN aLeft
 aLeft WordToWork DO <= =: Result
END // "меньше или равно"

BOOLEAN WORDWORKERFUNCTION "больше или равно" INTEGER IN aLeft
 aLeft WordToWork DO >= =: Result
END // "больше или равно"

Тогда наш пример можно переписать вот так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX "больше или равно" "Нижняя граница" ) И ( anX "меньше или равно" "Верхняя граница" ) IF
  true =: Result
 ELSE
  false =: Result
END // между

Ну или в конечном итоге:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX "больше или равно" "Нижняя граница" ) И ( anX "меньше или равно" "Верхняя граница" ) =: Result
END // между

Оператор IF - нам стал НЕ НУЖЕН. Он - "скрыт" во вспомогательных словах.

Аналогично:

BOOLEAN WORDWORKERFUNCTION меньше INTEGER IN aLeft
 aLeft WordToWork DO < =: Result
END // меньше 

BOOLEAN WORDWORKERFUNCTION больше INTEGER IN aLeft
 aLeft WordToWork DO > =: Result
END // больше 

И:

BOOLEAN WORDWORKERFUNCTION "строго между" INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX больше "Нижняя граница" ) И ( anX меньше "Верхняя граница" ) =: Result
END // "строго между"

Вот как-то так...

Понятное дело, что всё это относится и к операторам || и != и т.п. и т.п.

Позже - я ОБЯЗАТЕЛЬНО расскажу - "как это устроено внутри".

P.S. Хочу ещё "краем коснуться" вот этого - "Оператор IF - нам стал НЕ НУЖЕН. Он - "скрыт" во вспомогательных словах." - это - ВАЖНО. По моему ГЛУБОКОМУ убеждению - высокоуровневые тесты - ДОЛЖНЫ быть избавлены от управляющих конструкций типа IF, WHILE, LOOP, TRY и т.д. и т.п.

Они должны представлять собой лишь НАБОР ПОСЛЕДОВАТЕЛЬНЫХ действий. Которые либо проходят, либо - не проходят.

Это - ВАЖНО. НА САМОМ деле - ВАЖНО.

А управляющие конструкции ДОЛЖНЫ БЫТЬ "упакованы" во "вспомогательные слова" словарей.

Ещё раз. ЭТО - ВАЖНО.

Для того, чтобы тесты были понятны и ДЕТЕРМИНИРОВАНЫ.

GUI-тестирование "по-русски". Скажем так. "Анонс существующих возможностей" №2

Эта статья опять будет без отсылок к Delphi.

Да простят меня Borland и Embarcadero.

 Вот тут - http://18delphi.blogspot.ru/2013/11/gui_9015.html я описал использование слова WORDWORKER.

На самом деле - использование этого слова - позволяет ЗНАЧИТЕЛЬНО влиять на грамматику языка.

Это слово и подобные ему позволяют формировать "свой DSL" из аксиоматики заданной в коде скриптовой машины на Delphi (всё же одна отсылка есть)

 Приведу ещё один пример.

 Давайте узнаем входит ли число в заданный интервал.

Например между 2 и 10.

 В нашем языке конечно есть конструкция if и всякие разные операторы сравнения. Как они "устроены внутри" - я КОНЕЧНО ЖЕ позже расскажу.

А пока - вернёмся к нашей задаче.

 На "хардкорном" уровне поставленная выше задача решается так:

X 2 >= IF (
 X 10 <= IF
  true
 ELSE
  false
)
ELSE
 false

По-русски? Да что-то - СОВСЕМ не по-русски. Да и "обратная польская запись". Не все её любят.

 Поработаем над этим.

Ещё один "анонс" - у нас есть не только слово WORDWORKER, но и WORDWORKERFUNCTION. Название "то ещё". Но учитывая наличие слова WordAlias - любой желающий может придумать ему подходящий синоним и пользоваться им.

WORDWORKERFUNCTION отличается от WORDWORKER тем, что не только умеет работать с "параметрами слева" или "параметрами справа", но и возвращать значение.

Попробуем описать слова "между" и "строго между".

Выглядит это так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" >= IF (
  anX "Верхняя граница" <= IF
   true =: Result
  ELSE
   false =: Result
 )
 ELSE
  false =: Result
END //между

BOOLEAN WORDWORKERFUNCTION "строго между" INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" > IF (
  anX "Верхняя граница" < IF
   true =: Result
  ELSE
   false =: Result
 )
 ELSE
  false =: Result
END //между

Пример использования:

 
 3 между ( 2 5 ) // true
 0 между ( 2 5 ) // false
 2 между ( 2 5 ) // true
 5 между ( 2 5 ) // true
 10 между ( 2 5 ) // false

 3 "строго между" ( 2 5 ) // true
 2 "строго между" ( 2 5 ) // false
 5 "строго между" ( 2 5 ) // false
 0 "строго между" ( 2 5 ) // false
 10 "строго между" ( 2 5 ) // false

Ну и ещё есть "синтаксический сахар", что а_без_пробелов равно "а_без_пробелов" поэтому для "единообразия можно писать и так:

 3 "между" ( 2 5 ) // true
 0 "между" ( 2 5 ) // false
 2 "между" ( 2 5 ) // true
 5 "между" ( 2 5 ) // true
 10 "между" ( 2 5 ) // false

 3 "строго между" ( 2 5 ) // true
 2 "строго между" ( 2 5 ) // false
 5 "строго между" ( 2 5 ) // false
 0 "строго между" ( 2 5 ) // false
 10 "строго между" ( 2 5 ) // false

Ещё хотел написать о логических операторах.

Но по-моему - "и так много".

В общем - в следующей серии".

Сейчас я её напишу.

Следующая серия вот - http://18delphi.blogspot.com/2013/11/gui-3.html

среда, 6 ноября 2013 г.

GUI-тестирование "по-русски". Скажем так. "Анонс существующих возможностей"

Вот тут - http://18delphi.blogspot.ru/2013/11/gui_4.html я описал слово "Нажать". Которое позволяет нажимать клавиши и отправлять их текущему контролу.

Используется это слово так:

"Нажать {('Enter')}"
"Нажать {('Tab')}"
"Нажать {('Shift-Tab')}"
"Нажать {('Down')}"

А если надо нажать скажем 5 раз?

 Ну можно написать так:
"Нажать {('Down')}"
"Нажать {('Down')}"
"Нажать {('Down')}"
"Нажать {('Down')}"
"Нажать {('Down')}"

А если 100? А тысячу?

 На самом деле у нас в языке конечно есть циклы. Разные.

 Один из них - LOOP.

 Его использование выглядит так:

 5 LOOP "Нажать {('Down')}"

Этот пример нажимает клавишу "стрелка вниз" 5 раз. 

Кстати давайте его сразу перепишем:

Введём в словарь определение:

CONST "Стрелка вниз" 'Down'

Тогда пример можно переписать так:

 5 LOOP "Нажать {("Стрелка вниз")}"

По-русски? Да вроде бы - да, но не совсем.

Как это переписать "по-русски"?

До сих пор нам встречались определения новых слов такие как PROCEDURE и FUNCTION.

Они умеют работать с "параметрами слева", т.е. с теми значениями, которые помещены на стек значений.

Но. Есть и ещё одно слово для определения НОВЫХ пользовательских слов.

Называется - WORDWORKER.

Оно умеет работать не только с "параметрами слева", но и с "параметрами справа". Давайте посмотрим на пример его использования.

Напишем:
 WORDWORKER раз INTEGER IN aCount // - aCount это "параметр слева"
  aCount LOOP // - LOOP - уже знакомый нам оператор цикла
  ( 
   WordToWork // - WordToWork - "параметр справа", т.е. ссылка на то, что идёт за нашим словом
   DO // - вычисляет значение WordToWork. В терминах паскаля DO "эквивалентен" операции ^, или в терминах C - *
  )
 ; // раз 

Теперь наш пример можно написать так:

 5 раз "Нажать {("Стрелка вниз")}"

А если нам надо нажать 2 раза? Но пока это выглядит так:

 2 раз "Нажать {("Стрелка вниз")}"

По-русски? Вроде - да.

Но не совсем. Коряво очень. "По-колхозному".

Что можно сделать?

Можно конечно определить слово "раза", через WORDWORKER, но это не очень здорово. Дублирование кода и всё такое...

Но на наше счастье у нас в аксиоматике есть слово WordAlias. Которое занимается тем, что определяет синонимы к уже определённым словам.

При этом это именно - синонимы, а не вызов или повторение кода.

 Воспользуемся словом WordAlias:

 WordAlis раза раз
 // - это синоним к слову "раз"

Как только мы это написали, то мы можем писать так:

 5 раз "Нажать {("Стрелка вниз")}"
 2 раза "Нажать {("Стрелка вниз")}" 

Ну и ещё один штрих:

WordAlias "Стрелку вниз" "Стрелка вниз"

Тогда пример можно переписать так:

 5 раз "Нажать {("Стрелку вниз")}"
 2 раза "Нажать {("Стрелку вниз")}" 

Надеюсь, что идея понятна.

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

На самом деле при наличии слова WordAlias - изначально можно было написать так:

WordAlias раз LOOP
 // - это синоним к слову LOOP
WordAlis раза раз
 // - это синоним к слову "раз"

Все остальные примеры - остаются без изменений.

Думаю, что в ближайшем будущем - я напишу заметку о том, "как это устроено внутри" со стороны Delphi.

А следующая серия незамедлительно следует вот тут - http://18delphi.blogspot.com/2013/11/gui-2_6.html (в ближайшие 30 мин я её напишу). Но она тоже из разряда "анонсов".

P.S. Что характерно - мы в этой статье практически нигде не упомянули Delphi и код на нём.

Т.е. мы уже "достаточно быстро" оторвались от базового языка и начали оперировать терминами DSL (http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)

P.P.S. Можно кстати и "по-английски":

WordAlias times LOOP
 // - это синоним к слову LOOP
WordAlias "Press" "Нажать"

Тогда можно написать код:

5 times "Press {('Down')}"

Понятное дело, что грамматика остаётся ФОРМАЛЬНОЙ.

И есть всякие скобочки/закорючки.

И о "программировании на естественном языке" - речь не идёт.

 Но! Для тестов это по-моему - самое оно.

Тестировщик может открыть код теста и повторить ТО ЖЕ САМОЕ руками.

Конечно он должен знать (или догадываться) о назначении скобочек и закорючек.

Написали тут комментарий про UML и тесты

Поскольку он был публичный - позволю себе его процитировать:

"Ну типа того. Насчет УМЛ для ООП - все ок. А как быть с УМЛ для реляционных баз данных? Вот там тот самый избыточный код действительно даст катастрофическое падения перформанса, ибо оптимизация запросов - сложно формализуемый (неформализуемый) процесс. Ну, например расширения базового документа: добавление тагов, аттрибутов к нему в виде таблиц... Я так вообще использую фиребирд и ВСЯ логика сидит в БД. Это страшный сон. Зато очень эффективно.  Кстати, я вообще интересуюсь твоим опытом, ибо сейчас у меня создана довольно мощная библиотека, почти что фреймворк и практически все новые сущности в ней создаются по шаблонам, меня реально задолбало каждый раз писать одно и то же (почти одно и то же), например для создания нового типа документа на основе базового класса. Намного быстрее было бы это рисовать. Ну а в идеале я уже писал - рисуешь модель - на выходе готовый продукт, остается добавить имплементацию. А если система еще и сама учится... Должно наступить такое время, когда самый тупой манагер диктует роботу, чего он желает и через пару минут получает готовую апликуху, функционал которой только наглядно подтверждает тупость того самого манагера. И для того, чтобы манагер понял, что занимается не своим делом уйдет не пол-года а те самые пары минут"

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

Ну что сказать? Я ТАК И ДЕЛАЮ. Рисую, а не программирую.

"меня реально задолбало каждый раз писать одно и то же (почти одно и то же), например для создания нового типа документа на основе базового класса. Намного быстрее было бы это рисовать. Ну а в идеале я уже писал - рисуешь модель - на выходе готовый продукт, остается добавить имплементацию." - вот РОВНО так и делаю - РИСУЮ и ДОБАВЛЯЮ ИМПЛЕМЕНТАЦИЮ.

"Я конечно понимаю, что формализовать, довести до элементарных тест-кейсов каждое действие возможно (это вообще можно встроить в систему) и в результате система сможет тестировать себя сама" - у нас ИМЕННО так и устроено. Тесты встроены в систему. В отладочную версию.

И ещё одно мнение:

"Да, вопросы конечно правильные. Я тоже не раз задавался вопросами относительно правильного тестирования БДшных приложений. Там проблемы даже с тестовыми данными возможны - они сложные и сильно вариативные. И придумать как протестить, например, применение компенсаций и скидок с оборота при обслуживаниям по картам - не такая уж тривиальная задача. Но, думаю, если бы генерилось это дело с модели, всю картину происходящего было бы проще "обозреть"."

Не буду комментировать. Оставлю его открытым.

Прислали хорошую ссылку про монады

Вот - http://www.nuget.org/packages/Monads/

Там кроме всего прочего есть ясное, чёткое и логичное определение монад:

"In functional programming, a monad is a programming structure that represents computations. Monads are a kind of abstract data type constructor that encapsulate program logic instead of data in the domain model. A defined monad allows the programmer to chain actions together to build a pipeline to process data in various steps, in which each action is decorated with additional processing rules provided by the monad. Programs written in functional style can make use of monads to structure procedures that include sequenced operations, or to define some arbitrary control flows (like handling concurrency, continuations, side effects such as input/output, or exceptions)."

Offtopic. Хотел купить "себе лично" AQTime

Хотел купить "себе лично" AQTime.

Но эта  их политика, что "работает на одном компьютере" и надо "разворачивать сервер лицензий" - останавливает...

GUI-тестирование "по-русски". Заметка об уровнях тестирования

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/gui-2.html

Теперь хочется отметить вот что:

Я для себя выделяю несколько уровней для тестирования поведения компонент.

Разделяются они по тому - каким образом вызывается интересующий нас функционал.

Вот они:
1. Непосредственная посылка сообщения от мыши или клавиатуру.
2. Вызов Click или его аналогов непосредственно на интересующем нас контроле.
3. Вызов связанного TAction.
4. Вызов функционала бизнес-логики напрямую.

Каждый из перечисленных подходов - имеет право на жизнь и более того - служит для тестирования разных вещей.

"Непосредственная посылка сообщения от мыши или клавиатуру." - тестирует, что?
Это тестирует, что "реальный" ввод доходит до нашего контрола.

"Вызов OnClick или его аналогов непосредственно на интересующем нас контроле." - тестирует что?
Это тестирует, что у контрола определён метод Click и что он связан с нужной функциональностью.

"Вызов связанного TAction." - тестирует что?
Это тестирует тот факт, что в системе существует нужный TAction с нужной функциональностью. Пусть и не привязанной к контролам.

"Вызов функционала бизнес-логики напрямую." - тестирует что?
Это тестирует тот факт, что в системе есть нужная бизнес-логика, пусть и не привязанная к TAction или контролам, и что эта бизнес логика выполняется в соответствии с ТЗ.

Идея понятна? Надо приводить примеры?

Тут немного расшифрую.

Если есть подозрение, что "вообще бизнес-логика не работает", то надо тестировать её напрямую.

Если есть подозрение (или факты, косвенно подтверждаемые тем, что "по другой кнопке" - та же логика работает) что проблема в том, что контрол не обрабатывает ввод, то надо тестировать пункт 1 или пункт 2.

Что сказать про TAction? Когда его тестировать? Скорее всего тогда, когда TAction - доступен, а "Вызов функционала бизнес-логики напрямую." - либо недоступен, либо сложен в вызове.

Ну как-то так...

GUI-тестирование "по-русски". План статей, которые хочется написать

1. GUI-Тестирование "по-русски". Как всё устроено №2.5. Про TscriptContext
2. GUI-тестирование "по-русски". Как всё это устроено №7. Реализация пользовательского определения новых слов. PROCEDURE, FUNCTION, WORDWORKER etc.
3. GUI-тестирование "по-русски". Как всё это устроено №6. Реализация управляющих конструкций. IF, WHILE, TRY, LOOP
4. GUI-тестирование "по-русски". Как всё это устроено №5. Реализуем арифметические действия
5. GUI-тестирование "по-русски". Как всё это устроено №4. Регистрация слов аксиоматики
6. GUI-тестировании "по-русски". Как всё это устроено №3. Как устроена работа с переменными, оператор равно и что такое WordWorker'ы.
7. GUI-тестирование "по-русски". Заметка об уровнях тестирования
8. Написать про GUI-тестирование в терминах предметной области.
9. "роль и место функционального программирования в разрабокти бизнес-приложений"
10. Написать как скриптовые тесты интегрируются в DUnit

GUI-тестирование "по-русски". Как всё это устроено №2

Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/gui_5.html

Теперь хочется рассказать об устройстве TscriptCode и TscriptContext.

Для этого придётся вспомнить вот о чём:

Подсчёт ссылок и TRefcounted:


Абстрактные контейнеры:


Счастливы - "счастливые обладатели" ARC и generic'ов. Они могут сделать это по-другому. Но это (как говорил Фейнман (http://ru.wikipedia.org/wiki/%D0%A4%D0%B5%D0%B9%D0%BD%D0%BC%D0%B0%D0%BD,_%D0%A0%D0%B8%D1%87%D0%B0%D1%80%D0%B4_%D0%A4%D0%B8%D0%BB%D0%BB%D0%B8%D0%BF%D1%81)) - "читатели могут сделать это самостоятельно".

TscriptCode с одной стороны является наследником от TRefcounted, а с другой стороны - подмешивает в себя список ссылок на TscriptKeyWord.

Итак:

interface

  TscriptKeyWord = class(TRefcounted)
   procedure DoIt(aContext: TscriptContext); virtual; abstract;
    {* - собственно код выполнения слова. }
  end;//TscriptKeyWord

  _ItemType_ = TscriptKeyWord;
  _List_Parent_ = TRefcounted;

  {$Include _List_.imp.pas}

  TscriptCode = class(_List_)
   procedure CompileInteger(aValue: Integer);
   procedure CompileString(const aValue: String);
   procedure CompileKeyWord(aValue: TscriptKeyWord);
   procedure Run; // - выполняет скомпилированный код
   procedure RunInContext(aContext: TscriptContext); // - выполняет скомпилированный код в указанном контексте
   ...
  end;//TscriptCode

implementation

function IsSame(const A: _ItemType_;
  const B: _ItemType_): Boolean;
begin
 Result := (A = B);
end;//IsSame

procedure FreeItem(var thePlace: _ItemType_);
begin
 FreeAndNil(thePlace);
end;//FreeItem
 
procedure FillItem(var thePlace: _ItemType_;
  const aFrom: _ItemType_);
begin
 thePlace := aFrom.Use;
end;//FillItem

{$Include List.imp.pas}

procedure TscriptCode.CompileKeyWord(aValue: TscriptKeyWord);
begin
 Self.Add(aValue);
end;

procedure TscriptCode.Run;
var
 l_Context: TscriptContext;
begin
 l_Context := TscriptContext.Create;
 try
  RunInContext(l_Context);
 finally
  FreeAndNil(l_Context);
 end;//try..finally
end;

procedure TscriptCode.RunInContext(aContext: TscriptContext);
var
 l_Index : Integer;
begin
 for l_Index := 0 to Pred(Self.Count) do
  Self.Items[l_Index].DoIt(aContext); // - выполняем каждое компилированное слово
end;

type
 TkwInteger = class(TscriptKeyWord)
  private
   f_Value : Integer;
  protected
   procedure DoIt(aContext: TscriptContext); override;
  public
   constructor Create(aValue : Integer);
 end;//TkwInteger

procedure TkwInteger.DoIt(aContext: TscriptContext);
begin
 aContext.PushInteger(f_Value);
end;

constructor TkwIntegerCreate(aValue : Integer);
begin
 inherited Create;
 f_Value := aValue;
end;

procedure TscriptCode.CompileInteger(aValue: Integer);
var
 l_Integer : TkwInteger;
begin
 l_Integer := TkwInteger.Create(aValue);
 try
  CompileKeyWord(l_Integer);
 finally
  FreeAndNil(l_Integer);
 end;//try..finally
end;

type
 TkwString = class(TscriptKeyWord)
  private
   f_Value : String;
  protected
   procedure DoIt(aContext: TscriptContext); override;
  public
   constructor Create(const aValue : String);
 end;//TkwString

procedure TkwString.DoIt(aContext: TscriptContext);
begin
 aContext.PushString(f_Value);
end;

constructor TkwString.Create(const aValue : String);
begin
 inherited Create;
 f_Value := aValue;
end;

procedure TscriptCode.CompileString(const aValue: String);
var
 l_String : TkwString;
begin
 l_String := TkwString.Create(aValue);
 try
  CompileKeyWord(l_String);
 finally
  FreeAndNil(l_String);
 end;//try..finally
end;


Про TscriptContext - нету уже сил писать. Отвлёкся я на RTTI. Время - позднее...

Продолжу про TscriptContext в следующей серии. В общем там банально всё конечно. "Стек значений". Но - не совсем...

GUI-тестирование "по-русски". Использование "нового" RTTI для вызова методов объектов приложения

Вот тут - http://18delphi.blogspot.ru/2013/11/gui.html я описывал как в скриптовой машине публиковать "ручки" для вызова методов на контролах приложения.

В частности там была описана "ручка" (слово скриптовой машины) - ButtonClick.

Это слово было сделано на основе "прямого вызова" к методу контрола.

Примерно вот так:

procedure TkwButtonClick.DoIt(aContext : TscriptContext);
var
 l_Component : TComponent;
begin
 l_Component := aContext.PopObject As TComponent;
 Assert(l_Component Is TButton);
 (l_Component As TButton).Click;
end;

Но ни для кого не секрет, что какой-то из Delphi XEN - принёс нам с собой - "новый" RTTI.

Где очень удобно работать с информацией о методах объектов.

Попробуем применить эту технику для реализации "более низкоуровневого" слова ObjectMethodWithoutParametersCall.

Вызов метода объекта без параметров.

 А уже через это "низкоуровневое слово" мы определим слово ButtonClick как "просто слово" пользовательского словаря.

Понятное дело, что всё это относится к Delphi XEN (не помню версии).

Итак. Опишем наше слово (сама работа с RTTI - "срисована" отсюда - http://delphi2010.ru/?p=218):

interface

 TkwObjectMethodWithoutParametersCall = class(TscriptKeyWord)
  protected
   procedure DoIt(aContext: TscriptContext); override;
 end;//TkwObjectMethodWithoutParametersCall

implementation

procedure TkwObjectMethodWithoutParametersCall.DoIt(aContext: TscriptContext);
var
 l_Object : TObject;
 l_MethodName : String;
 l_t : TRttiType;
 l_ctx : TRttiContext;
 l_ObjectMethod : TRttiMethod;
begin
 l_Object := aContext.PopObject;
 l_MethodName := aContext.PopString;
 l_ctx := TRttiContext.Create;
  l_t := l_ctx.GetType(l_Object.ClassInfo);
  l_ObjectMethod := l_t.GetMethod(l_MethodName);
  if (l_ObjectMethod.GetParameters.Count = 0) then
   l_ObjectMethod.Invoke(l_Object, [])
  else
   Assert(false);
 l_ctx.Free;
end;

initialization
 ScriptEngine.RegisterKeyWord(TkwObjectMethodWithoutParametersCall, 'ObjectMethodWithoutParametersCall');

Повторюсь. Код использования "нового" RTTI - я "срисовывал". И не пробовал сам. Хотя документация выглядит - БОЛЕЕ чем привлекательно.

Но я хотел проиллюстрировать лишь идею.

Когда сам попробую - приведу конечный вариант.

Но "навскидку" - всё вроде бы правильно. И логично.

Итак.

Как же теперь выглядит наш метод ButtonClick с учётом наличия ObjectMethodWithoutParametersCall?

А вот так:

PROCEDURE ButtonClick OBJECT IN aControl
 'Click' aControl ObjectMethodWithoutParametersCall
END // ButtonClick 

Опять же ремарка - обработку ошибок - я пока опустил.

 Всё остальное - остаётся без изменений.

 В частности например вот это:

PROCEDURE "Нажать кнопку" OBJECT IN aButton
 aButton ButtonClick
END //"Нажать кнопку"

вторник, 5 ноября 2013 г.

GUI-тестирование "по-русски". Использование "классического" RTTI для работы со свойствами объектов приложения

Вот тут - http://18delphi.blogspot.ru/2013/11/gui_4.html я описывал как можно изменить свойство объекта приложения при помощи "специально выделенной ручки".

Там было описано слово EditSetText.


Теперь мы попробуем использовать RTTI для примерно тех же целей.


Напишем более "низкоуровневые" слова на Delphi. 


А слово EditSetText - определим уже в "пользовательском словаре".

Итак:


Вот что мы имеем "со стороны Delphi":



interface

 TkwObjectGetStringProp = class(TscriptKeyWord)
  protected
   procedure DoIt(aContext: TscriptContext); override;
 end;//TkwObjectGetStrProp 

 TkwObjectSetStringProp = class(TscriptKeyWord)
  protected
   procedure DoIt(aContext: TscriptContext); override;
 end;//TkwObjectGetStrProp

implementation

procedure TkwObjectGetStringProp.DoIt(aContext: TscriptContext);
var
 l_Object : TObject;
 l_PropertyName : String;
begin
 l_Object := aContext.PopObject;
 l_PropertyName := aContext.PopString;
 aContext.PushString(GetStrProp(l_Object, l_PropertyName));
end;

procedure TkwObjectSetStringProp.DoIt(aContext: TscriptContext);
var
 l_Object : TObject;
 l_PropertyName : String;
 l_PropertyValue : String;
begin
 l_Object := aContext.PopObject;
 l_PropertyName := aContext.PopString;
 l_PropertyValue := aContext.PopString;
 SetStrProp(l_Object, l_PropertyName, l_V);
 SetStrProp(l_Object, l_PropertyName, l_PropertyValue);
end;

initialization
 ScriptEngine.RegisterKeyWord(TkwObjectGetStringProp, 'ObjectGetStringProp');
 ScriptEngine.RegisterKeyWord(TkwObjectSetStringProp, 'ObjectSetStringProp');

Обработку ошибок - пока намерено опускаю.

 Понятное, дело, что точно так же можно определить и ObjectGetIntegerProp, ObjectSetIntegerProp и ObjectGetBooleanProp, ObjectSetBooleanProp ти т.д. и т.п.

 Теперь как "преображается" наше слово EditSetText:

PROCEDURE EditSetText STRING IN aText OBJECT IN aControl
 aText 'Text' aControl ObjectSetStringProp
END // EditSetText

В "пару" ему можно написать слово получения текста:

FUNCTION EditGetText OBJECT IN aControl
 'Text' aControl ObjectGetStringProp
END // EditSetText

Весь остальной код предыдущего примера - остаётся без изменений.

Т.е. например слово "Присвоить текст в текущий контрол" - остаётся без изменений:

PROCEDURE "Присвоить текст в текущий контрол" STRING IN aText
 OBJECT VAR l_Control
 FocusedControl =: l_Control
 aText l_Control EditSetText
END //"Присвоить текст в текущий контрол"

Обработку ошибок тут - я опять же, пока - намеренно - опустил.

GUI-тестирование "по-русски". Ремарка о том, "как это всё устроено"

Предыдущая серия была тут - http://18delphi.blogspot.com/2013/11/gui_2510.html

Теперь пару слов о том как это устроено.

(Пишу "с листа" по-памяти)

Есть парсер, который разбивает входной поток на токены.

Интерфейс парсера примерно такой:
type
 TscriptTokenType = (ttInteger, ttString, ttKeyWord);

 IscriptParser = interface
  function IsEOF : Boolean;
  procedure NextToken;
  function TokenType: TscriptTokenType;
  function IntegerToken: Integer;
  function StringToken: String;
  function KeyWordToken: TscriptKeyWord;
 end;//IscriptParser

Есть скриптовая машина - которая парсит текст при помощи парсера и компилирует его.

Основная процедура скриптовой машины примерно такая:
type
  TscriptContext = class
   function PopInteger: Integer;
   procedure PushInteger(aValue : Integer);
   function PopString: String;
   procedure PushString(const aValue : String);
   function PopObject: TObject;
   procedure PushObject(aValue : TObject);
   ...
  end;//TscriptContext
  
  TscriptKeyWord = class
   function IsImmediate: Boolean; virtual;
    {* - является ли слово НЕПОСРЕДСТВЕННО выполняемым? Таким как PROCEDURE, FUNCTION, VAR, CONST etc. }
   procedure DoIt(aContext: TscriptContext); virtual; abstract;
    {* - собственно код выполнения слова. }
  end;//TscriptKeyWord

  TscriptCode = class
   procedure CompileInteger(aValue: Integer);
   procedure CompileString(const aValue: String);
   procedure CompileKeyWord(aValue: TscriptKeyWord);
   procedure Run; // - выполняет скомпилированный код
   procedure RunInContext(aContext: TscriptContext); // - выполняет скомпилированный код в указанном контексте
   ...
  end;//TscriptCode
  
  TscriptCompileContext = class(TscriptContext)
   property Parser: IscriptParser;
    {* - Текущий парсер. }
   property Code: TscriptCode;
    {* - Текущий компилируемый код. }
  end;//TscriptCompileContext

procedure TscriptEngine.Compile(const aParser : IscriptParser; aCode : TscriptCode);
var
 l_Context : TscriptCompileContext;
begin
 l_Context := TscriptCompileContext.Create;
 try
  l_Context.Parser := aParser; 
  // - Это чтобы слова НЕПОСРЕДСТВЕННОГО выполнения могли выполняться и управлять при этом текущим парсером
  l_Context.Code := aCode; // - место куда компилируется код
  while not aParser.IsEOF do
  begin
   Case aParser.TokenType of
    ttInteger : 
     aCode.CompileInteger(aParser.IntegerToken); // - скомпилируем целое значение
    ttStringr : 
     aCode.CompileString(aParser.StringToken); // - скомпилируем строковое значение
    ttKeyWord :
    begin
     if aParser.KeyWordToken.IsImmediate then
      // - слово НЕПОСРЕДСТВЕННОГО выполнения, такое как PROCEDURE, FUNCTION, VAR, CONST etc. 
      //   ("пользовательские" слова - тоже могут быть такими)
      aParser.KeyWordToken.DoIt(l_Context) 
      // - выполним данное слово в контексте компиляции, 
      //   при этом такие слова могут управлять как парсером, так и компилируемым кодом
     else
      aCode.CompileKeyWord(aParser.KeyWordToken); // - скомпилируем вызов слова
    end;//ttKeyWord
    else
     Assert(false); // - мало ли, что ещё появится (а оно - кстати есть)
   end;//Case aParser.TokenType
   aParser.NextToken;
  end;//while not aParser.IsEOF
 finally
  FreeAndNil(l_Context);
 end;//try..finally
end;//TscriptEngine.Compile

И собственно процесс компиляции и запуска:

procedure RunCode(const aParser : IscriptParser);
var
 l_Code : TscriptCode;
 l_ScriptEngine : TscriptEngine;
begin
 l_Code := TscriptCode.Create;
 try
  l_ScriptEngine := TscriptEngine.Create;
  try
   l_ScriptEngine.Compile(aParser, l_Code);
   // - компилируем код из входного потока
  finally
   FreeAndNil(l_ScriptEngine);
  end;//try..finally
  l_Code.Run; 
  // - можно выполнить код - сразу, 
  //   но в принципе - можно его запомнить куда-нибудь во внешний объект 
  //   и выполнять несколько раз
 finally
  FreeAndNil(l_Code);
 end;//try..finally
end;

Продолжение ОБЯЗАТЕЛЬНО следует. Я уже написал план из ещё восьми статей. Надеюсь "в конце пути" - заинтересованные читатели смогут сами собрать скриптовую/тестовую машину из запчастей.

понедельник, 4 ноября 2013 г.

GUI-тестирование "по-русски". Ещё одна ремарка

Предыдущая серия было тут - http://18delphi.blogspot.ru/2013/11/gui_2510.html

Хочется ещё раз отметить вот какую вещь - никто не борется за "написание искусственного интеллекта" или "разбор текста на естественном языке".

Это мне кажется утопией.

"По-русски" - это ДЛЯ ТОГО, чтобы тесты максимально напоминали TestCase'ы.

Чтобы в ситуации когда автоматический тест не прошёл - тестировщик не искал бы описание того, что делает тест. А просто - открыл бы код теста и повторил его руками. И проверил бы как система реагирует на "ручное протыкивание".

GUI-тестирование "по-русски". Ввод "алфавитно-цифровых" символов в текущий контрол

Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/gui_4.html

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

Рассмотрим ещё одно слово.

Для ввода "алфавитно-цифровых" символов.

Со стороны Delphi это выглядит так:
interface

 TkwEmitString = class(TscriptKeyWord)
  {* Позволяет ввести строку.
Пример:
[code]
PROCEDURE "Ввести строку" STRING IN aString
 aString EmitString
END //"Ввести строку"
[code] }
 protected
 // realized methods
   procedure DoIt(aContext: TscriptContext); override;
 end;//TkwEmitString

implementation

procedure TkwEmitString.DoIt(aContext: TscriptContext);
var
 l_Index : Integer;
 l_C     : Integer;
 l_H     : THandle;
 l_S     : String;
begin
 l_S := aContext.PopString;
 l_H := Windows.GetFocus;
 Assert(l_H <> 0);
 for l_Index := 1 to Length(l_S) do
 begin
  l_C := ORD(l_S[l_Index]);
  if (Windows.GetFocus <> l_H) then
   Windows.SetFocus(l_H);
  // - иногда (например под отладчиком) фокус "убегает" из контрола - поэтому тут вставлена эта "заплатка"
  SendMessage(l_H, WM_CHAR, l_C, 0);
 end;//for l_Index
end;//TkwEmitString.DoIt

initialization
 ScriptEngine.RegisterKeyWord(TkwEmitString, 'EmitString');

И пример использования:

 'Привет мир!' EmitString

И "вспомогательное" слово:

PROCEDURE "Ввести строку" STRING IN aString
 aString EmitString
END //"Ввести строку"

И тогда пример переписывается так:

 "Ввести строку {('Привет мир!')}"

Или с учётом ремарки (http://18delphi.blogspot.ru/2013/11/gui_1949.html) вот так:

 'Привет мир!' "Ввести строку"

Есть одна тонкость. Этот код работает с русскими буквами правильно, если клавиатура переключена на русскую локаль. Как это сделать из кода - я напишу чуть позднее.

А пока - закончим.

О GUI-тестировании "по-русски". Короткая ремарка

Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/gui_4.html

Хочу отметить одно обстоятельство.

В контексте нашей скриптовой машины инфиксная запись (http://ru.wikipedia.org/wiki/%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80_(%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)#.D0.97.D0.B0.D0.BF.D0.B8.D1.81.D1.8C) вида:
 "Сделать что-то с параметром {(aParam1)} и параметром {(aParam2)}"

Эквивалентна обратной польской записи (http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%B0%D1%8F_%D0%BF%D0%BE%D0%BB%D1%8C%D1%81%D0%BA%D0%B0%D1%8F_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C) вида:

 aParam1 aParam2 "Сделать что-то с параметром и параметром"

Выражение в двойных кавычках - "Сделать что-то с параметром и параметром" - это всего лишь идентификатор слова (предложения) из словаря.

Поскольку наша скриптовая машина базируется на форт-машине, то обратная польская запись для неё "родная", а инфиксная - лишь служит неким "синтаксическим сахаром" (http://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BD%D1%82%D0%B0%D0%BA%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D1%81%D0%B0%D1%85%D0%B0%D1%80).

 Т.е. можно написать так:

"Нажать {('Enter')}"
"Нажать {('Tab')}"
"Нажать {('Shift-Tab')}"

а можно так:

 'Enter' "Нажать"
 'Tab' "Нажать"
 'Shift-Tab' "Нажать"

- эти два примера - равнозначны.

О GUI-тестировании "по-русски". Развиваем тему

Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/gui.html

Попробуем развить тему.

Давайте попробуем поработать с контролом в фокусе и например ввести в него текст.

Это можно сделать несколькими способами.

Например через эмуляцию нажатия кнопок или непосредственно присваивая свойство Caption/Text.

И тот и другой способ - имеют право на жизнь. И служат разным целям.

Эмуляция нажатия кнопок - проверяет как контрол обрабатывает клавиатурный ввод. Непосредственное присваивание позволяет управлять состоянием контрола, для проверки дальнейшей бизнес-логики приложений.

Это разные уровни тестирования. Об уровнях тестирования я может быть расскажу как-нибудь потом.

А пока реализуем слова тестовой машины для работы с контролами.

interface

TkwFocusedControl = class(TscriptKeyWord)
protected
 procedure DoIt(aContext : TscripContext); override;
end;//TkwFocusedControl

TkwEditSetText = class(TscriptKeyWord)
protected
 procedure DoIt(aContext : TscripContext); override;
end;//TkwEditSetText

implementation

procedure TkwFocusedControl.DoIt(aContext : TscripContext);
var
 l_Control : TControl;
begin
 l_Control := FindControl(Windows.GetFocus);
 Assert(l_Control <> nil);
 aContext.PushObject(l_Control);
end;

procedure TkwEditSetText.DoIt(aContext : TscripContext);
var
 l_Text : String;
 l_Control : TControl;
begin
 l_Control := aContext.PopObject;
 l_Text := aContext.PopString;
 Assert(l_Control Is TEdit);
 (l_Control As TEdit).Text := l_Text;
 // - это можно было бы сделать и через RTTI, но мы сделаем это напрямую
end;

initialization
 ScriptEngine.RegisterWord(TkwFocusedControl, 'FocusedControl');
 ScriptEngine.RegisterWord(TkwEditSetText, 'EditSetText');

Теперь напишем тест:

OBJECT VAR l_Control
FocusedControl =: l_Control
'Текст' l_Control EditSetText

Он делает, что и задумывалось - находит контрол в фокусе и присваивает ему заданный текст.

Теперь напишем вспомогательное слово:

PROCEDURE "Присвоить текст в текущий контрол" STRING IN aText
 OBJECT VAR l_Control
 FocusedControl =: l_Control
 aText l_Control EditSetText
END //"Присвоить текст в текущий контрол"

Тест теперь можно переписать так:

"Присвоить текст {('Текст')} в текущий контрол"

Опять же - "почти похоже" на кусок TestCase'а.

"На закуску" - реализуем слово для эмуляции нажатия на кнопку клавиатуры в контроле в фокусе.

Оно потом нам понадобится.

Вот это слово:

interface
 TkwKey = class(TscriptKeyWord)
  {* Нажатие на кнопку клавиатуры.
Пример:
[code]
PROCEDURE "Нажать" STRING IN aString
 aString Key
END // "Нажать"
[code] }
 protected
 // realized methods
   procedure DoIt(aContext: TscriptContext); override;
 end;//TkwKey

implementation

procedure TkwKey.DoIt(aContext: TscriptContext);
type
 TSS = ssShift..ssDouble;
const
 cMap : array [TSS] of Integer = (VK_Shift, VK_MENU, VK_Control,
                                  0, 0, 0, 0);
var
 l_SC : TShortCut;
 l_K  : Word;
 l_Shift: TShiftState;
 l_ShiftToCheck: TShiftState;
 l_H    : THandle;
 l_KeyState: TKeyboardState;
 l_NewKeyState: TKeyboardState;
 I : Integer;
 l_SS : TSS;
 l_AltOnly : Boolean;
 l_Alt : Boolean;
begin
 l_H := GetFocus;
 l_SC := TextToShortCut(aContext.PopString);
 Assert(l_SC <> 0);
 ShortCutToKey(l_SC, l_K, l_Shift);
 l_ShiftToCheck := l_Shift;
 l_AltOnly := false;
 l_Alt := false;
 l_Alt := ssAlt in l_ShiftToCheck;
 GetKeyboardState(l_KeyState);
 try
  for I := Low(l_NewKeyState) to High(l_NewKeyState) do
   l_NewKeyState[I] := 0;
  for l_SS := Low(l_SS) to High(l_SS) do
  begin
   if (l_SS in l_Shift) then
   begin
    if (cMap[l_SS] <> 0) then
    begin
     l_ShiftToCheck := l_ShiftToCheck - [l_SS];
     l_NewKeyState[cMap[l_SS]] := $81;
     SetKeyboardState(l_NewKeyState);
     if (cMap[l_SS] = vk_Menu) then
     begin
      //PostMessage(l_H, WM_KEYDOWN, cMap[l_SS], $20380001);
     end//cMap[l_SS] = vk_Menu
     else
      PostMessage(l_H, WM_KEYDOWN, cMap[l_SS], $1510001);
     ProcessMessages;
    end;//cMap[l_SS] <> 0
   end;//l_SS in l_Shift
  end;//for l_SS
  Assert(l_ShiftToCheck = []);
  l_NewKeyState[l_K] := $81;
  SetKeyboardState(l_NewKeyState);
  if l_AltOnly then
  begin
   SendMessage(l_H, WM_SYSCHAR, l_K, 0);
  end//l_AltOnly
  else
  begin
   if l_Alt then
    PostMessage(l_H, WM_SYSKEYDOWN, l_K, $20170001)
   else
    PostMessage(l_H, WM_KEYDOWN, l_K, $1510001);
   ProcessMessages;
   if l_Alt then
    PostMessage(l_H, WM_SYSKEYUP, l_K, $E0170001)
   else
    PostMessage(l_H, WM_KEYUP, l_K, $1510001);
   ProcessMessages;
  end;//l_AltOnly
  for l_SS := Low(l_SS) to High(l_SS) do
  begin
   if (l_SS in l_Shift) then
   begin
    if (cMap[l_SS] <> 0) then
    begin
     if (cMap[l_SS] = vk_Menu) then
     begin
      //PostMessage(l_H, WM_KEYUP, cMap[l_SS], $C0380001);
     end//cMap[l_SS] = vk_Menu
     else
      PostMessage(l_H, WM_KEYUP, cMap[l_SS], $1510001);
     ProcessMessages;
    end;//cMap[l_SS] <> 0
   end;//l_SS in l_Shift
  end;//for l_SS
 finally
  SetKeyboardState(l_KeyState);
 end;//try..finally
end;//TkwKey.DoIt

initialization
 ScriptEngine.RegisterKeyWord(TkwKey, 'Key');

Теперь напишем тест:

'Enter' Key
'Tab' Key
'Shift-Tab' Key

Ну и опять напишем "вспомогательное слово":

PROCEDURE "Нажать" STRING IN aString
 aString Key
END // "Нажать"

Тогда тест можно переписать так:

 "Нажать {('Enter')}"
 "Нажать {('Tab')}"
 "Нажать {('Shift-Tab')}"

Опять - "почти по-русски".

Мне кажется, что подобный "код" может читать и исполнять человек.

На этом - пока закончим.