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

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')}"

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

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

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