GUI-тестирование "по-русски". Прикручиваем DUnit к нашим "скриптам"
Предыдущая серия была тут - http://18delphi.blogspot.ru/2013/11/gui-back-to-basics_22.html
Теперь давайте сделаем вот какую штуку - прикрутим DUnit к нашим скриптам.
Для начала сделаем тестовый VCL проект по аналогии с FM проектом.
Он более чем прост:
Код проекта:
Код главной формы:
ВСЕ ОСТАЛЬНЫЕ проектные файлы ОСТАЛИСЬ БЕЗ ИЗМЕНЕНИЙ.
Сама публикация скриптов в DUnit - БОЛЕЕ, чем ПРОСТА:
Код "тестового набора":
Тут код for l_FileName in TDirectory.GetFiles - перебирает файлы в директории по маске.
Спасибо Роману Янковскому (http://roman.yankovsky.me/) за подсказку (http://programmingmindstream.blogspot.ru/2013/12/blog-post_16.html?showComment=1387279738393#c6313390821290709952).
Ну и код ОТДЕЛЬНО ВЗЯТОГО скриптового теста:
Что тут написано?
БАНАЛЬНО - перебираем файлы по маске *.script в директории EXE-файла и делаем для КАЖДОГО такого файла ОТДЕЛЬНЫЙ DUnit-тест.
Запускаем наш проект и видим две формы:
Главная форма приложения:
И форма со списком тестов:
Ну и ещё одна ремарка. Понятное дело, что ТАКИМ ЖЕ образом можно "размножать" тесты относительно ЛЮБОГО параметра итератора. А не только относительно скриптового файла.
Я позже планирую написать как можно "размножать" тесты относительно входа/выхода.
Ну примерно как в библиотеке ссылку на которую мне давал Роман Янковский, где тесты размножаются относительно своих атрибутов. Вот тут - http://18delphi.blogspot.ru/2013/04/blog-post_7108.html - про это собраны ссылки.
Ну и в "скриптовой аксиоматике" сделано одно ДОПУЩЕНИЕ.
Вот тут:
- "активной" формой считается ЛЮБАЯ форма ОТЛИЧНАЯ от формы DUnit (TGUITestRunner). Понятно, что это всего лишь "пример".
И в РЕАЛЬНОЙ ЖИЗНИ - это "легко исправляется".
ПОКА Draft проекта лежит тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/GUITests/Chapter3/
Тема интересна? Писать про неё в этот weekend?
Или писать про "морду" для FM проектов для DUnit?
... to be continued ...
Предыдущая серия была тут - 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 ...


Мне тут два юнита про DUnit интересны. Ты по-другому сделал. Надо будет попробовать твой способ, к моему у меня есть пара косметических претензий.
ОтветитьУдалитьНу скажем так. У меня на работе сделано сильно сложнее. Просто мы тогда "не умели готовить" DUnit. Да и были там "странности" связанные со временем инициализации TestSuite. Здесь же я всё ПЕРЕОСМЫСЛИЛ и написал по-новой. "С листа". По-моему получился - минимально возможный вариант.
УдалитьТвой вариант я просмотрел "по диагонали". Ну в общем "нареканий" у меня не возникло.
Ну посмотри мой нынешний вариант. Может быть будет чем-нибудь полезно.
"Мне тут два юнита про DUnit интересны"
УдалитьНу в общем собственно про них пост и был :-) Хотя на поверку можно было бы их в один объединить.
openctf ?
ОтветитьУдалить"openctf ?"
ОтветитьУдалить-- Что, простите?
http://sourceforge.net/projects/openctf/
ОтветитьУдалитьПонял...
Ну что же. На "ту же тему". Только не совсем.
Да и начал я не позже их.