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

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

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

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

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

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

Он более чем прост:

Код проекта:

program VCLProject;

uses
  Vcl.Forms,
  Script.Engine in 'Scripting\Script.Engine.pas',
  Script.Parser in 'Scripting\Script.Parser.pas',
  Script.Interfaces in 'Scripting\Script.Interfaces.pas',
  Core.Obj in 'Core\Core.Obj.pas',
  Testing.Engine in 'Testing\Testing.Engine.pas',
  Script.WordsInterfaces in 'Scripting\Script.WordsInterfaces.pas',
  Script.Code in 'Scripting\Script.Code.pas',
  Script.Dictionary in 'Scripting\Script.Dictionary.pas',
  Script.Word in 'Scripting\Script.Word.pas',
  Script.StringWord in 'Scripting\Script.StringWord.pas',
  Script.UnknownToken in 'Scripting\Script.UnknownToken.pas',
  Script.Axiomatics in 'Scripting\Script.Axiomatics.pas',
  Script.Word.Examples in 'Scripting\Script.Word.Examples.pas',
  Script.Word.Buttons in 'Scripting\Script.Word.Buttons.pas',
  VCLForm1 in 'VCLForm1.pas' {Form1},
  GUITestRunner,
  DUnit.Scripting.AutoTests in 'DUnitScripting\DUnit.Scripting.AutoTests.pas',
  DUnit.Scripting.AutoTest in 'DUnitScripting\DUnit.Scripting.AutoTest.pas';

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  //  GUITestRunner.RunRegisteredTests;
  GUITestRunner.RunRegisteredTestsModeless;
  Application.Run;
end.

Код главной формы:

unit VCLForm1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,

  Script.Interfaces
  ;

type
  TForm1 = class(TForm, IscriptCompileLog, IscriptRunLog)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Edit1: TEdit;
    CompileLog: TMemo;
    RunLog: TMemo;
    Run: TButton;
    procedure Button1Click(Sender: TObject);
    procedure RunClick(Sender: TObject);
  private
    { Private declarations }
   procedure IscriptCompileLog_Log(const aString: String);
   procedure IscriptCompileLog.Log = IscriptCompileLog_Log;
   procedure IscriptRunLog_Log(const aString: String);
   procedure IscriptRunLog.Log = IscriptRunLog_Log;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
 Testing.Engine,
 Script.Engine
 ;

{$R *.dfm}

procedure TForm1.IscriptCompileLog_Log(const aString: String);
begin
 CompileLog.Lines.Add(aString);
 {$IfNDef NoTesting}
 TtestEngine.CurrentTest.SocketMetric(TtestSocket.Create(Self, 'IscriptCompileLog_Log')).PutValue(aString);
 {$EndIf  NoTesting}
end;

procedure TForm1.IscriptRunLog_Log(const aString: String);
begin
 RunLog.Lines.Add(aString);
 {$IfNDef NoTesting}
 TtestEngine.CurrentTest.SocketMetric(TtestSocket.Create(Self, 'IscriptRunLog_Log')).PutValue(aString);
 {$EndIf  NoTesting}
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 Edit1.Text := (Sender As TButton).Caption + ' clicked';
end;

procedure TForm1.RunClick(Sender: TObject);
const
 l_FileName = 'FirstScript.script';
begin
 CompileLog.Lines.Clear;
 RunLog.Lines.Clear;
 {$IfNDef NoTesting}
 TtestEngine.StartTest(l_FileName);
 try
 {$EndIf  NoTesting}
  TScriptEngine.RunScript(l_FileName, Self, Self);
 {$IfNDef NoTesting}
 finally
  TtestEngine.StopTest;
 end;//try..finally
 {$EndIf  NoTesting}
end;

end.

ВСЕ ОСТАЛЬНЫЕ проектные файлы ОСТАЛИСЬ БЕЗ ИЗМЕНЕНИЙ.

Сама публикация скриптов в DUnit - БОЛЕЕ, чем ПРОСТА:

Код "тестового набора":

unit DUnit.Scripting.AutoTests;

interface

uses
  TestFrameWork,
  Core.Obj
  ;

type
 TautoTests = class(TTestSuite)
  public
   procedure AddTests(testClass: TTestCaseClass); override;
 end;//TautoTests

implementation

uses
  DUnit.Scripting.AutoTest,
  System.IOUtils,
  System.SysUtils,
  Testing.Engine
  ;

procedure TautoTests.AddTests(testClass: TTestCaseClass);
var
 l_FileName : String;
begin
 Assert(testClass.InheritsFrom(TautoTest));
 {$IfNDef NoTesting}
 TtestEngine.StartTest('Initialization');
 try
 {$EndIf  NoTesting}
  for l_FileName in TDirectory.GetFiles(ExtractFilePath(ParamStr(0)), '*.script') do
  begin
   {$IfNDef NoTesting}
   TtestEngine.CurrentTest.SocketMetric(TtestSocket.Create(Self, 'AddTests')).PutValue(l_FileName);
   {$EndIf  NoTesting}
   AddTest(RautoTest(testClass).Create(l_FileName));
//   TScriptEngine.RunScript(l_FileName, Self, Self);
  end;//for l_FileName in TDirectory
 {$IfNDef NoTesting}
 finally
  TtestEngine.StopTest;
 end;//try..finally
 {$EndIf  NoTesting}
 //inherited;
end;

initialization
 TestFramework.RegisterTest(TautoTests.Create(TautoTest));

end.

Тут код for l_FileName in TDirectory.GetFiles - перебирает файлы в директории по маске.

Спасибо Роману Янковскому (http://roman.yankovsky.me/) за подсказку (http://programmingmindstream.blogspot.ru/2013/12/blog-post_16.html?showComment=1387279738393#c6313390821290709952).

Ну и код ОТДЕЛЬНО ВЗЯТОГО скриптового теста:

unit DUnit.Scripting.AutoTest;

interface

uses
  TestFrameWork
  ;

type
 TautoTest = class(TTestCase)
   public
    constructor Create(MethodName: string); override;
   protected
     procedure DoIt;
 end;//TautoTest

 RautoTest = class of TautoTest;

implementation

uses
  Script.Engine,
  Testing.Engine
  ;

constructor TautoTest.Create(MethodName: string);
begin
 inherited Create(MethodName);
 FMethod := DoIt;
end;

procedure TautoTest.DoIt;
begin
 {$IfNDef NoTesting}
 TtestEngine.StartTest(FTestName);
 try
 {$EndIf  NoTesting}
  TScriptEngine.RunScript(FTestName, nil, nil);
 {$IfNDef NoTesting}
 finally
  TtestEngine.StopTest;
 end;//try..finally
 {$EndIf  NoTesting}
// TScriptEngine.RunScript(FTestName, nil, nil);
end;

end.

Что тут написано?

БАНАЛЬНО - перебираем файлы по маске *.script в директории EXE-файла и делаем для КАЖДОГО такого файла ОТДЕЛЬНЫЙ DUnit-тест.

Запускаем наш проект и видим две формы:

Главная форма приложения:


И форма со списком тестов:


Ну и ещё одна ремарка. Понятное дело, что ТАКИМ ЖЕ образом можно "размножать" тесты относительно ЛЮБОГО параметра итератора. А не только относительно скриптового файла.

Я позже планирую написать как можно "размножать" тесты относительно входа/выхода.

Ну примерно как в библиотеке ссылку на которую мне давал Роман Янковский, где тесты размножаются относительно своих атрибутов. Вот тут - http://18delphi.blogspot.ru/2013/04/blog-post_7108.html  - про это собраны ссылки.

Ну и в "скриптовой аксиоматике" сделано одно ДОПУЩЕНИЕ.

Вот тут:

procedure TkwFindComponent.DoDoIt(aContext: TscriptContext);
var
 l_Name : String;
 l_Component : TComponent;
 l_ActiveForm : TForm;
 l_Index : Integer;
begin
 l_Name := aContext.PopString;
 Assert(l_Name <> '');
 l_ActiveForm := nil;
 for l_Index := 0 to Pred(Screen.FormCount) do
  if (Screen.Forms[l_Index].ClassName <> 'TGUITestRunner') then
  begin
   l_ActiveForm := Screen.Forms[l_Index];
   break;
  end;//Screen.Forms[l_Index].ClassName <> 'TGUITestRunner'
 Assert(l_ActiveForm <> nil);
 l_Component := l_ActiveForm.FindComponent(l_Name);
 Assert(l_Component <> nil);
 aContext.PushObject(l_Component);
end;

- "активной" формой считается ЛЮБАЯ форма ОТЛИЧНАЯ от формы DUnit (TGUITestRunner). Понятно, что это всего лишь "пример".

И в РЕАЛЬНОЙ ЖИЗНИ - это "легко исправляется".

ПОКА Draft проекта лежит тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/GUITests/Chapter3/

Тема интересна? Писать про неё в этот weekend?

Или писать про "морду" для FM проектов для DUnit?

... to be continued ...

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

  1. Мне тут два юнита про DUnit интересны. Ты по-другому сделал. Надо будет попробовать твой способ, к моему у меня есть пара косметических претензий.

    ОтветитьУдалить
    Ответы
    1. Ну скажем так. У меня на работе сделано сильно сложнее. Просто мы тогда "не умели готовить" DUnit. Да и были там "странности" связанные со временем инициализации TestSuite. Здесь же я всё ПЕРЕОСМЫСЛИЛ и написал по-новой. "С листа". По-моему получился - минимально возможный вариант.

      Твой вариант я просмотрел "по диагонали". Ну в общем "нареканий" у меня не возникло.

      Ну посмотри мой нынешний вариант. Может быть будет чем-нибудь полезно.

      Удалить
    2. "Мне тут два юнита про DUnit интересны"

      Ну в общем собственно про них пост и был :-) Хотя на поверку можно было бы их в один объединить.

      Удалить
  2. http://sourceforge.net/projects/openctf/

    Понял...

    Ну что же. На "ту же тему". Только не совсем.

    Да и начал я не позже их.

    ОтветитьУдалить