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/
ОтветитьУдалитьПонял...
Ну что же. На "ту же тему". Только не совсем.
Да и начал я не позже их.