Original in Russian: http://18delphi.blogspot.ru/2013/04/blog-post_6244.html
GUI-testing. Table of contents
I’ll start with a lyrical digression.
Once I had a computer BK-0010.01. I paid a fantastic sum of 500 roubles for it.
Anyway, I could not connect it to my TV-set. The TV-set had no AV-input. “For video tape recorders”.
My father and I solved the problem. Under my father’s keen guidance I got to know that any radio and television equipment has “checkpoints”. These are the “pins” on the board to which either sensors (for example, oscillograph) or signal sources (for example, frequency generator) are connected.
We found the required checkpoint. It was next to the high and low frequency conversion stage. I can well remember, it called KT-26.
My father and I soldered a connector and connected the computer to it.
And so I entered the happy world of “home computers”. I could learn the architecture of PDP-11 in practice.
Later, when I mastered the technique, I repeated this trick a few times on my own, without my father, with computers and TV-sets of my friends. All were happy. The key thing was taking circuit from TV-set and finding the checkpoint with required parameters in it. So to speak, “matching the required connector with the required INTERFACE”.
Now, let’s get down to business.
When I “seriously” went in for “unit”-testing, I remembered about this technique and decided to apply it in practice. Luckily, it proved to be very usable.
I’ll try to give an example.
Unfortunately – it can not be compiled. I can’t discover the whole code (for the reasons mentioned here - http://18delphi.blogspot.com/2013/04/disclaimer.html ). However, I hope the idea will be clear from the snippet I’ll give. It is not simple, not just three lines. But, as for me, very significant.
So.
Let’s get back to my first post - http://18delphi.blogspot.com/2013/03/blog-post.html
In it I gave a glowing explanation that the test of print preview was my “first one” in DUnit technology. Which is actually quite true.
Now, let’s see how it looks like.
The test itself:
The test implements interfaces IafwPreviewPanel, InevShapesLogger, IafwPagesLogger and connects to the “connector” (checkpoint) TnevShapesPaintedSpy and to the connector TafwPreviewPageSpy.
The interfaces look like this:
Now, the connector TnevShapesPaintedSpy looks like this:
The connector TafwPreviewPageSpy looks like this:
Now, about where these connectors are called:
It is so somehow. I’ve notified – the example is not simple. But the tested code is complex too. I have been debugging print preview and print for two years. It is still not perfect. But now I see – “tomorrow” – what and where has gone wrong.
The resulting (not the abstract) classes look something like this:
Or like this:
The name of the file for check is found from the name of the test class (using ClassName and ClassType), and the file with the name is in the test repository.
You would say - "the presence of checkpoints “fouls” the system and it acts not like without them”. And you’d be ABSOLUTELY right. It would be appropriate to recollect Heisenberg’s uncertainty principle. I completely agree with you. Even testers tell me about it, although they are “not supposed” to know the system’s internal organization. They are ALSO RIGHT. Yes. This approach does not work at 100%, but it is terribly (!) usable. It allows to fulfil many testing tasks. It is not a “silver bullet”. It is only one of the working approaches addressing the special tasks of testing of the SPECIAL code. It is not the “spherical cow”, but – it works.
Better than NOTHING. It’s better to have “boss-eyed” ONE test than have none of a hundred perfect ones.
Try is. May be you will like it.
P.S. The missing classes:
GUI-testing. Table of contents
I’ll start with a lyrical digression.
Once I had a computer BK-0010.01. I paid a fantastic sum of 500 roubles for it.
Anyway, I could not connect it to my TV-set. The TV-set had no AV-input. “For video tape recorders”.
My father and I solved the problem. Under my father’s keen guidance I got to know that any radio and television equipment has “checkpoints”. These are the “pins” on the board to which either sensors (for example, oscillograph) or signal sources (for example, frequency generator) are connected.
We found the required checkpoint. It was next to the high and low frequency conversion stage. I can well remember, it called KT-26.
My father and I soldered a connector and connected the computer to it.
And so I entered the happy world of “home computers”. I could learn the architecture of PDP-11 in practice.
Later, when I mastered the technique, I repeated this trick a few times on my own, without my father, with computers and TV-sets of my friends. All were happy. The key thing was taking circuit from TV-set and finding the checkpoint with required parameters in it. So to speak, “matching the required connector with the required INTERFACE”.
Now, let’s get down to business.
When I “seriously” went in for “unit”-testing, I remembered about this technique and decided to apply it in practice. Luckily, it proved to be very usable.
I’ll try to give an example.
Unfortunately – it can not be compiled. I can’t discover the whole code (for the reasons mentioned here - http://18delphi.blogspot.com/2013/04/disclaimer.html ). However, I hope the idea will be clear from the snippet I’ll give. It is not simple, not just three lines. But, as for me, very significant.
So.
Let’s get back to my first post - http://18delphi.blogspot.com/2013/03/blog-post.html
In it I gave a glowing explanation that the test of print preview was my “first one” in DUnit technology. Which is actually quite true.
Now, let’s see how it looks like.
The test itself:
unit PreviewTestBefore;
// Generated from UML model, root element: TestCase::Class Shared Delphi Operations For Tests::TestFormsTest::Everest::TPreviewTestBefore235875079
//
// Test of constructing Preview
interface
uses
afwInterfaces,
TextEditorVisitor,
evHAFPainterEx,
nevShapesPaintedSpy,
afwPreviewPageSpy,
l3Interfaces,
PrimTextLoad_Form,
nevTools,
afwPreviewPage
;
type
TPreviewTest = {abstract} class(TTextEditorVisitor, IafwPreviewPanel, InevShapesLogger, IafwPagesLogger)
{* Test of constructing Preview }
private
// private fields
f_Done : Boolean;
f_Now : Cardinal;
{* Time of test launch}
protected
// realized methods
procedure SetCurrentPage(aValue: Integer);
procedure Invalidate;
procedure Done;
procedure pm_SetPreviewCanvas(const aValue: IafwPreviewCanvas);
function pm_GetPainted: Boolean;
procedure DoVisit(aForm: TPrimTextLoadForm); override;
{* Process the text }
function OpenLog(const aView: InevView): AnsiString;
procedure CloseLog(const aLogName: AnsiString);
function LogScreen(const aView: InevView): Boolean;
procedure LogPage(aPage: TafwPreviewPage;
aCounter: Boolean);
function ShouldStop: Boolean;
protected
// overridden protected methods
procedure InitFields; override;
function FileForOutput: AnsiString; override;
{* Standard output file, for the current test }
function GetNormalFontSize: Integer; override;
{* Returns the size of the font of the “normal” style. 0 – by default }
function GetFolder: AnsiString; override;
{* Folder containing the test }
function RaiseIfEtalonCreated: Boolean; override;
protected
// protected fields
f_LogNumber : Integer;
f_CurrentOutput : AnsiString;
protected
// protected methods
function PageFileName(aNumber: Integer;
aWidthNumber: Integer;
aCounter: Boolean;
anEtalon: Boolean): AnsiString;
{* Name of the file for saving the page }
procedure ReadColontituls(var theColontituls: TevColontituls); virtual;
function GetHAFFontSize: Integer; virtual;
{* Size of page headers. 0 – by default }
end;//TPreviewTest
implementation
uses
Windows,
evStyleInterface,
evdStyles,
SysUtils,
vtPreviewPanel,
Controls,
Forms,
l3String,
l3Stream,
l3Types,
imageenio,
l3FileUtils,
Graphics,
Classes,
KTestRunner,
evPreviewForTestsTuning,
l3Defaults,
l3CanvasPrim,
TestFrameWork
;
// start class TPreviewTestBefore
function TPreviewTest.PageFileName(aNumber: Integer;
aWidthNumber: Integer;
aCounter: Boolean;
anEtalon: Boolean): AnsiString;
var
l_Et : String;
l_C : String;
l_WN : String;
begin
if aCounter then
l_C := 'C_'
else
l_C := '';
if (aWidthNumber = 0) then
l_WN := ''
else
l_WN := '.' + l3LeftPadChar(IntToStr(aWidthNumber), 2, '0');
if anEtalon then
l_Et := EtalonSuffix
else
l_Et := '';
Result := Format('%s%s.%s%s%s%s.png',
[OutputPath,
KPage,
l_C,
l3LeftPadChar(IntToStr(aNumber), 4, '0'),
l_WN,
l_Et
]);
end;//TPreviewTest.PageFileName
procedure TPreviewTest.ReadColontituls(var theColontituls: TevColontituls);
begin
// - we do nothing, everything is by default
end;//TPreviewTest.ReadColontituls
function TPreviewTest.GetHAFFontSize: Integer;
begin
Result := 0;
end;//TPreviewTest.GetHAFFontSize
procedure TPreviewTest.SetCurrentPage(aValue: Integer);
begin
// - we do nothing
end;//TPreviewTest.SetCurrentPage
procedure TPreview.Invalidate;
begin
// - we do nothing
CheckTimeout(f_Now, 120 * 60 * 1000)
// - we check if we loop
end;//TPreviewTest.Invalidate
procedure TPreviewTest.Done;
begin
f_Done := true;
end;//TPreviewTest.Done
procedure TPreviewTest.pm_SetPreviewCanvas(const aValue: IafwPreviewCanvas);
begin
// - we do nothing
end;//TPreviewTest.pm_SetPreviewCanvas
function TPreviewTest.pm_GetPainted: Boolean;
begin
Result := false;
end;//TPreviewTest.pm_GetPainted
procedure TPreviewTest.DoVisit(aForm: TPrimTextLoadForm);
var
PP : TvtPreviewPanel;
l_OnReadColontituls : TevReadColontitulsEvent;
l_SI : TevStyleInterface;
l_Size : Integer;
l_NewSize : Integer;
begin
TafwPreviewPageSpy.Instance.SetLogger(Self);
try
TnevShapesPaintedSpy.Instance.SetLogger(Self);
try
l_SI := TevStyleInterface.Make;
try
l_SI.SelectStyle(ev_saHFRight);
l_NewSize := GetHAFFontSize;
l_Size := l_SI.Font.Size;
try
if (l_NewSize > 0) then
begin
l_SI.SelectStyle(ev_saHFLeft);
l_SI.Font.Size := l_NewSize;
l_SI.SelectStyle(ev_saHFRight);
l_SI.Font.Size := l_NewSize;
end;//l_NewSize > 0
l_OnReadColontituls := g_OnReadColontituls;
try
g_OnReadColontituls := Self.ReadColontituls;
// --------------
// There is the code one has to uncomment if he wishes to see
// the results of preview constructing
(* try
PP := TvtPreviewPanel.Create(aForm);
PP.Align := alClient;
PP.Parent := aForm;
PP.Preview := aForm.Text.Preview;
PP.ZoomToPage(1,1,true);
except
end;//try..except
repeat
try
Application.ProcessMessages;
except
end;//try..except
until Application.Terminated;
Exit;*)
// --------------
StartTimer;
try
f_Now := GetTickCount;
aForm.Text.Preview.Update(Self);
finally
StopTimer('Preview.Update');
end;//try..finally
finally
g_OnReadColontituls := l_OnReadColontituls;
end;//try..finally
Check(f_Done);
finally
l_SI.SelectStyle(ev_saHFLeft);
l_SI.Font.Size := l_Size;
l_SI.SelectStyle(ev_saHFRight);
l_SI.Font.Size := l_Size;
end;//try..finally
finally
FreeAndNil(l_SI);
end;//try..finally
finally
TnevShapesPaintedSpy.Instance.RemoveLogger(Self);
end;//try..finally
finally
TafwPreviewPageSpy.Instance.RemoveLogger(Self);
end;//try..finally
end;//TPreviewTest.DoVisit
function TPreviewTest.OpenLog(const aView: InevView): AnsiString;
var
l_Cnv : InevInfoCanvas;
l_C : String;
l_WN : String;
l_Page : Integer;
procedure MakeName;
begin//MakeName
Result := Format('%s%s.%s%s%s.shapes',
[OutputPath,
KPage,
l_C,
l3LeftPadChar(IntToStr(l_Page), 4, '0'),
l_WN
]);
end;//MakeName
begin
//Inc(f_LogNumber);
l_Cnv := aView.Metrics.InfoCanvas;
if l_Cnv.IsPagesCounter then
l_C := 'C_'
else
l_C := '';
if (f_LogNumber = 0) then
l_WN := ''
else
l_WN := '.' + l3LeftPadChar(IntToStr(f_LogNumber), 2, '0');
f_LogNumber := l_Cnv.PageWidthNumber;
l_Page := l_Cnv.PageNumber;
if (l_Page > 1) then
Dec(l_Page)
else
Assert(l_Page = 1);
MakeName;
//Result := OutputPath + KPage + '.' + l3LeftPadChar(IntToStr(f_LogNumber), 4, '0') + '.shapes';
if (Result = f_CurrentOutput) then
begin
Inc(l_Page);
MakeName;
end;//Result = f_CurrentOutput
f_CurrentOutput := Result;
end;//TPreviewTest.OpenLog
procedure TPreviewTest.CloseLog(const aLogName: AnsiString);
var
l_N : String;
l_Extra : String;
l_Counter : Boolean;
begin
l_N := ExtractFileName(aLogName);
l_Counter := (Pos('.C_', l_N) > 0);
if l_Counter then
l_Extra := ''
else
l_Extra := ChangeFileExt(l_N, '.png');
CheckOutputWithInput(ChangeFileExt(l_N, EtalonSuffix + '.shapes'),
#0,
l_Extra,
not l_Counter);
end;//TPreviewTest.CloseLog
function TPreviewTest.LogScreen(const aView: InevView): Boolean;
begin
with aView.Metrics.InfoCanvas do
Result := Printing AND not IsVirtual;
end;//TPreviewTest.LogScreen
procedure TPreviewTest.LogPage(aPage: TafwPreviewPage;
aCounter: Boolean);
var
l_EN : String;
l_N : String;
l_IO : TImageEnIO;
l_B : Graphics.TBitmap;
l_CVSPath : String;
l_CVS : String;
begin
if aCounter then
Exit;
if (aPage.PageNumber <= 0) then
Exit;
l_EN := PageFileName(aPage.PageNumber, aPage.PageWidthNumber, aCounter, true);
l_N := PageFileName(aPage.PageNumber, aPage.PageWidthNumber, aCounter, false);
l_IO := TImageEnIO.Create(nil);
try
l_B := Graphics.TBitmap.Create;
try
l_B.PixelFormat := pf24bit;
l_B.Width := Trunc(IafwPreviewPage(aPage).GetMMWidth * 0.01 * 96 / 25.4);
l_B.Height := Trunc(IafwPreviewPage(aPage).GetMMHeight * 0.01 * 96 / 25.4);
IafwPreviewPage(aPage).DrawTo(Rect(0, 0, l_B.Width, l_B.Height), l_B);
l_IO.Bitmap := l_B;
l_IO.Params.BitsPerSample := 8;
l_IO.Params.SamplesPerPixel := 1;
l_IO.SaveToFilePNG(l_N);
finally
FreeAndNil(l_B);
end;//try..finally
finally
FreeAndNil(l_IO);
end;//try..finally
if not IsWritingToK then
begin
if not FileExists(l_EN) then
CopyFile(l_N, l_EN);
if not IsFakeCVS then
begin
l_CVSPath := g_CVSPath + '\' + TestSetFolderName + '\';
if DirectoryExists(l_CVSPath) then
begin
l_CVS := l_CVSPath + ExtractFileName(l_EN);
if not FileExists(l_CVS) then
begin
CopyFile(l_N, l_CVS);
ToLog(Format('The etalon to be loaded to CVS has been made - "%s"', [l_CVS]));
end;//not FileExists(l_CVS)
end;//DirectoryExists(l_CVSPath)
end;//not IsFakeCVS
end;//not IsWritingToK
end;//TPreviewTest.LogPage
procedure TPreviewTest.InitFields;
begin
inherited;
f_LogNumber := 0;
f_CurrentOutput := '';
end;//TPreviewTest.InitFields
function TPreviewTest.FileForOutput: AnsiString;
begin
Assert(f_CurrentOutput <> '');
Result := f_CurrentOutput;
end;//TPreviewTest.FileForOutput
function TPreviewTest.GetNormalFontSize: Integer;
begin
Result := 12;
end;//TPreviewTest.GetNormalFontSize
function TPreviewTest.RaiseIfEtalonCreated: Boolean;
begin
Result := false;
end;//TPreviewTest.RaiseIfEtalonCreated
end.
The test implements interfaces IafwPreviewPanel, InevShapesLogger, IafwPagesLogger and connects to the “connector” (checkpoint) TnevShapesPaintedSpy and to the connector TafwPreviewPageSpy.
The interfaces look like this:
IafwPreviewPanel = interface(IafwBase)
{* Panel Print-preview. }
['{2DF654AF-58CD-4D9D-8F24-E3696D42EB3A}']
procedure pm_SetPreviewCanvas(const aValue: IafwPreviewCanvas);
function pm_GetPainted: Boolean;
procedure SetCurrentPage(aValue: Integer);
procedure Invalidate;
procedure Done;
property PreviewCanvas: IafwPreviewCanvas
write pm_SetPreviewCanvas;
property Painted: Boolean
read pm_GetPainted;
end;//IafwPreviewPanel
InevShapesLogger = interface(IUnknown)
{* Log of the drawn objects }
['{D33CDAF3-2F4B-422C-879E-56B02F0686F9}']
function OpenLog(const aView: InevView): AnsiString;
procedure CloseLog(const aLogName: AnsiString);
function LogScreen(const aView: InevView): Boolean;
end;//InevShapesLogger
IafwPagesLogger = interface(IUnknown)
['{19361554-2DE2-4E58-B896-503677BDD13B}']
procedure LogPage(aPage: TafwPreviewPage;
aCounter: Boolean);
end;//IafwPagesLogger
Now, the connector TnevShapesPaintedSpy looks like this:
unit nevShapesPaintedSpy;
// Generated from UML model, root element: SimpleClass::Class Shared Delphi::Everest::Views::TnevShapesPaintedSpy
//
// “Watchdog” of the drawn objects. {RequestLink:235864309}
interface
uses
l3Filer,
nevTools,
nevShapesPainted,
l3ProtoObject
;
type
InevShapesLogger = interface(IUnknown)
{* Log of the drawn object }
['{D33CDAF3-2F4B-422C-879E-56B02F0686F9}']
function OpenLog(const aView: InevView): AnsiString;
procedure CloseLog(const aLogName: AnsiString);
function LogScreen(const aView: InevView): Boolean;
end;//InevShapesLogger
TnevShapesPaintedSpy = class(Tl3ProtoObject)
{* “Watchdog” of the drawn objects. [RequestLink:235864309] }
private
// private fields
f_Logger : InevShapesLogger;
f_Filer : Tl3CustomFiler;
protected
// overridden protected methods
procedure Cleanup; override;
{* Function of object fields cleaning. }
procedure ClearFields; override;
public
// public methods
procedure LogShapes(const aView: InevView;
aShapes: TnevBaseTopShape);
{* Logs the drawn objects }
procedure SetLogger(const aLogger: InevShapesLogger);
procedure RemoveLogger(const aLogger: InevShapesLogger);
class function Exists: Boolean;
function LogScreen(const aView: InevView): Boolean;
public
// singleton factory method
class function Instance: TnevShapesPaintedSpy;
{- returns a singleton instance. }
end;//TnevShapesPaintedSpy
implementation
uses
l3Base {a},
SysUtils,
l3Types,
k2Tags,
l3String,
evParaTools,
nevBase,
l3MinMax
;
// start class TnevShapesPaintedSpy
var g_TnevShapesPaintedSpy : TnevShapesPaintedSpy = nil;
procedure TnevShapesPaintedSpyFree;
begin
FreeAndNil(g_TnevShapesPaintedSpy);
end;
class function TnevShapesPaintedSpy.Instance: TnevShapesPaintedSpy;
begin
if (g_TnevShapesPaintedSpy = nil) then
begin
l3System.AddExitProc(TnevShapesPaintedSpyFree);
g_TnevShapesPaintedSpy := Create;
end;
Result := g_TnevShapesPaintedSpy;
end;
procedure TnevShapesPaintedSpy.LogShapes(const aView: InevView;
aShapes: TnevBaseTopShape);
procedure LogShape(aShape : TnevShape);
function MangleCoord(aValue : Integer): Integer;
// - here we mangle the coordinates ON PURPOSE, so that tests were done on a larger number of test machines
function EpsilonIt(aValue : Integer): Integer;
begin//EpsilonIt
if (aValue > 0) then
Result := Max(0, aValue - 20)
else
if (aValue < 0) then
Result := Min(0, aValue + 20)
else
Result := aValue;
end;//EpsilonIt
begin//MangleCoord
Result := (EpsilonIt(aValue) div 100) * 100;
end;//MangleCoord
var
l_Index : Integer;
l_ImageInfo : PnevControlImageInfo;
begin//LogShape
if (aShape <> nil) then
begin
f_Filer.WriteLn('----');
with aShape.__Obj do
begin
f_Filer.WriteLn(Format('Obj type = %s', [TagType.AsString]));
if HasSubAtom(k2_tiText) then
f_Filer.WriteLn(Format('Text = ''%s''', [l3ReplaceNonReadable(StrA[k2_tiText])]));
end;//with aShape.__Obj
if (aShape.Count <> 0) then
f_Filer.WriteLn(Format('Count = %d', [aShape.Count]));
with aShape.Bounds do
f_Filer.WriteLn(Format('Rect = (%d, %d, %d, %d)', [MangleCoord(Top),
MangleCoord(Left),
MangleCoord(Bottom),
MangleCoord(Right)]));
Assert(aShape.__FI <> nil);
with aShape.__FI do
begin
if (Width <> 0) OR (Height <> 0) then
f_Filer.WriteLn(Format('Dim = (%d, %d)', [MangleCoord(Width),
MangleCoord(Height)]));
if Hidden then
f_Filer.WriteLn(Format('Hidden = %d', [Ord(Hidden)]));
if (MaxLinesCount <> 0) then
f_Filer.WriteLn(Format('MaxLinesCount = %d', [MaxLinesCount]));
if (DeltaHeight <> 0) then
f_Filer.WriteLn(Format('DeltaHeight = %d', [MangleCoord(DeltaHeight)]));
if (Zoom <> 100) then
f_Filer.WriteLn(Format('Zoom = %d', [Zoom]));
if (Lines <> nil) then
if (Lines.Count <> 1) then
f_Filer.WriteLn(Format('LinesCount = %d', [Lines.Count]));
end;//with aShape.__FI
if LogScreen(aView) and evHasOwnStyle(aShape.__Obj) then
begin
l_ImageInfo := aShape.__FI.ImageInfo;
if (l_ImageInfo.rFirstIndex > -1) or (l_ImageInfo.rLastIndex > -1) then
begin
f_Filer.WriteLn('----');
f_Filer.WriteLn(Format('ImageInfo FirstIndex = %d, LastIndex = %d', [l_ImageInfo.rFirstIndex, l_ImageInfo.rLastIndex]));
end; // if EvHasOwnStyle(aShape.__Obj) then
end; // if EvHasOwnStyle(aShape.__Obj) then
for l_Index := 0 to Pred(aShape.Count) do
LogShape(aShape.Items[l_Index]);
end;//aShape <> nil
end;//LogShape
var
l_LogName : String;
begin
if (f_Logger <> nil) then
begin
l_LogName := f_Logger.OpenLog(aView);
try
f_Filer := Tl3CustomDOSFiler.Make(l_LogName, l3_fmWrite);
try
f_Filer.Open;
try
LogShape(aShapes);
finally
f_Filer.Close;
end;//try..finally
finally
FreeAndNil(f_Filer);
end;//try..finally
finally
f_Logger.CloseLog(l_LogName);
end;//try..finally
end;//f_Logger <> nil
end;//TnevShapesPaintedSpy.LogShapes
procedure TnevShapesPaintedSpy.SetLogger(const aLogger: InevShapesLogger);
begin
Assert(f_Logger = nil);
f_Logger := aLogger;
end;//TnevShapesPaintedSpy.SetLogger
procedure TnevShapesPaintedSpy.RemoveLogger(const aLogger: InevShapesLogger);
begin
Assert(f_Logger = aLogger);
f_Logger := nil;
end;//TnevShapesPaintedSpy.RemoveLogger
class function TnevShapesPaintedSpy.Exists: Boolean;
begin
Result := (g_TnevShapesPaintedSpy <> nil);
end;//TnevShapesPaintedSpy.Exists
function TnevShapesPaintedSpy.LogScreen(const aView: InevView): Boolean;
begin
Result := (f_Logger <> nil) AND f_Logger.LogScreen(aView);
end;//TnevShapesPaintedSpy.LogScreen
procedure TnevShapesPaintedSpy.Cleanup;
begin
FreeAndNil(f_Filer);
inherited;
end;//TnevShapesPaintedSpy.Cleanup
procedure TnevShapesPaintedSpy.ClearFields;
{-}
begin
f_Logger := nil;
inherited;
end;//TnevShapesPaintedSpy.ClearFields
end.
The connector TafwPreviewPageSpy looks like this:
unit afwPreviewPageSpy;
// Generated from UML model, root element: SimpleClass::Class Shared Delphi::AFW::Draw::TafwPreviewPageSpy
//
// “Watchdog” of TafwPreviewPage, for {RequestLink:235873282}
//
interface
uses
afwPreviewPage,
l3ProtoObject
;
type
IafwPagesLogger = interface(IUnknown)
['{19361554-2DE2-4E58-B896-503677BDD13B}']
procedure LogPage(aPage: TafwPreviewPage;
aCounter: Boolean);
end;//IafwPagesLogger
TafwPreviewPageSpy = class(Tl3ProtoObject)
{* “Watchdog” of TafwPreviewPage, for [RequestLink:235873282] }
private
// private fields
f_Logger : IafwPagesLogger;
protected
// overridden protected methods
procedure ClearFields; override;
public
// public methods
class function Exists: Boolean;
procedure SetLogger(const aLogger: IafwPagesLogger);
procedure RemoveLogger(const aLogger: IafwPagesLogger);
procedure LogPage(aPage: TafwPreviewPage;
aCounter: Boolean);
public
// singleton factory method
class function Instance: TafwPreviewPageSpy;
{- returns a singleton instance. }
end;//TafwPreviewPageSpy
implementation
uses
l3Base {a}
;
// start class TafwPreviewPageSpy
var g_TafwPreviewPageSpy : TafwPreviewPageSpy = nil;
procedure TafwPreviewPageSpyFree;
begin
FreeAndNil(g_TafwPreviewPageSpy);
end;
class function TafwPreviewPageSpy.Instance: TafwPreviewPageSpy;
begin
if (g_TafwPreviewPageSpy = nil) then
begin
l3System.AddExitProc(TafwPreviewPageSpyFree);
g_TafwPreviewPageSpy := Create;
end;
Result := g_TafwPreviewPageSpy;
end;
class function TafwPreviewPageSpy.Exists: Boolean;
begin
Result := (g_TafwPreviewPageSpy <> nil);
end;//TafwPreviewPageSpy.Exists
procedure TafwPreviewPageSpy.SetLogger(const aLogger: IafwPagesLogger);
begin
Assert(f_Logger = nil);
f_Logger := aLogger;
end;//TafwPreviewPageSpy.SetLogger
procedure TafwPreviewPageSpy.RemoveLogger(const aLogger: IafwPagesLogger);
begin
Assert(f_Logger = aLogger);
f_Logger := nil;
end;//TafwPreviewPageSpy.RemoveLogger
procedure TafwPreviewPageSpy.LogPage(aPage: TafwPreviewPage;
aCounter: Boolean);
begin
if (f_Logger <> nil) then
f_Logger.LogPage(aPage, aCounter);
end;//TafwPreviewPageSpy.LogPage
procedure TafwPreviewPageSpy.ClearFields;
{-}
begin
f_Logger := nil;
inherited;
end;//TafwPreviewPageSpy.ClearFields
end.
Now, about where these connectors are called:
procedure TnevShapesPainted.Clear;
{* - Clears the list. }
begin
try
if (f_Shapes <> nil) then
begin
if (f_View <> nil) then
begin
if TnevShapesPaintedSpy.Exists then
if TnevShapesPaintedSpy.Instance.LogScreen(InevView(f_View)) then
TnevShapesPaintedSpy.Instance.LogShapes(InevView(f_View), f_Shapes);
end;//f_View <> nil
end;//f_Shapes <> nil
finally
FreeAndNil(f_Current);
FreeAndNil(f_Shapes);
inherited;
end;//try..finally
end;
And:
procedure TafwPreviewPage.Drop(aCounter: Boolean);
var
l_Stream : IStream;
l_Index : Integer;
begin
if (f_MetaFile <> nil) then
begin
if aCounter then
FreeAndNil(f_MetaFile)
else
if (f_DropStream = nil) then
begin
if TafwPreviewPageSpy.Exists then
TafwPreviewPageSpy.Instance.LogPage(Self, aCounter);
f_DropStream := TevDataCache.CreateTempStream;
try
l_Stream := f_DropStream.MakeForWrite;
try
MetaFile.SaveToIStream(l_Stream);
finally
l_Stream := nil;
end;//try..finally
FreeAndNil(f_MetaFile);
except
f_DropStream := nil;
raise;
end;//try..except
end//f_DropStream = nil
else
FreeAndNil(f_MetaFile);
end;//f_MetaFile <> nil
for l_Index := 1 to Pred(WidthCount) do
WidthPage(l_Index).Drop(aCounter);
end;//TafwPreviewPage.Drop
It is so somehow. I’ve notified – the example is not simple. But the tested code is complex too. I have been debugging print preview and print for two years. It is still not perfect. But now I see – “tomorrow” – what and where has gone wrong.
The resulting (not the abstract) classes look something like this:
unit K182157315;
// Generated from UML model, root element: TestCase::Class Shared Delphi Tests::DailyTest::7.7::K182157315
//
// {RequestLink:182157315}
//
interface
uses
Classes
,
PreviewTest
;
type
TK182157315 = class(TPreviewTest)
{* [RequestLink:182157315] }
end;//TK182157315
implementation
uses
TestFrameWork
;
// start class TK182157315
initialization
TestFramework.RegisterTest(TK182157315.Suite);
end.
Or like this:
unit K219124975;
// Generated from UML model, root element: TestCase::Class Shared Delphi Tests::DailyTest::7.5::K219124975
//
// {RequestLink:219124975}
//
interface
uses
Classes
,
PreviewTest
,
evHAFPainterEx
;
type
TK219124975 = class(TPreviewTest)
{* [RequestLink:219124975] }
protected
// overridden protected methods
function TreatExceptionAsSuccess: Boolean; override;
function GetNormalFontSize: Integer; override;
{* Returns the size of the font of “normal” style. 0 – by default }
procedure ReadColontituls(var theColontituls: TevColontituls); override;
function GetHAFFontSize: Integer; override;
{* Size of page headers. 0 - by default }
function GetFolder: AnsiString; override;
{* Folder containing the test }
function GetModelElementGUID: AnsiString; override;
{* Model element identifier that describes the test }
end;//TK219124975
implementation
uses
evTypes,
l3Base,
TestFrameWork
;
// start class TK219124975
function TK219124975.TreatExceptionAsSuccess: Boolean;
begin
Result := true;
end;//TK219124975.TreatExceptionAsSuccess
function TK219124975.GetNormalFontSize: Integer;
begin
Result := 39;
end;//TK219124975.GetNormalFontSize
procedure TK219124975.ReadColontituls(var theColontituls: TevColontituls);
begin
inherited;
theColontituls[pcUpRightFirst] := l3CStr('%DocFullName%'#10'%DocRedactionDate%');
theColontituls[pcUpRight] := theColontituls[pcUpRightFirst];
theColontituls[pcDownRightFirst] := l3CStr(''{'%DocCurrentPage% / %DocPagesCount%'});
theColontituls[pcDownRight] := theColontituls[pcDownRightFirst];
end;//TK219124975.ReadColontituls
function TK219124975.GetHAFFontSize: Integer;
begin
Result := 28;
end;//TK219124975.GetHAFFontSize
initialization
TestFramework.RegisterTest(TK219124975.Suite);
end.
The name of the file for check is found from the name of the test class (using ClassName and ClassType), and the file with the name is in the test repository.
You would say - "the presence of checkpoints “fouls” the system and it acts not like without them”. And you’d be ABSOLUTELY right. It would be appropriate to recollect Heisenberg’s uncertainty principle. I completely agree with you. Even testers tell me about it, although they are “not supposed” to know the system’s internal organization. They are ALSO RIGHT. Yes. This approach does not work at 100%, but it is terribly (!) usable. It allows to fulfil many testing tasks. It is not a “silver bullet”. It is only one of the working approaches addressing the special tasks of testing of the SPECIAL code. It is not the “spherical cow”, but – it works.
Better than NOTHING. It’s better to have “boss-eyed” ONE test than have none of a hundred perfect ones.
Try is. May be you will like it.
P.S. The missing classes:
unit TextEditorVisitor;
// The library "TestFormsTest"
// Generated from UML model, root element: TestCase::Class Shared Delphi Operations For Tests::TestFormsTest::Everest::TTextEditorVisitor
//
// Test that works with text of the document using the editor but does not change it
//
interface
uses
TextEditorVisitorPrim
;
type
TTextEditorVisitor = {abstract} class(TTextEditorVisitorPrim)
{* Test that works with text of the document using the editor but does not change it
// protected methods
function GetNormalFontSize: Integer; virtual;
{* Returns the size of the font of “normal” style. 0 – by default }
function MaxHeight: Integer; virtual;
{* If it does not return 0, the cycle of choosing the height from FormExtent.Y to MaxHeight will be set }
published
// published methods
procedure DoIt;
{* The body of the test }
end;//TTextEditorVisitor
implementation
uses
SysUtils,
evStyleInterface,
TestFrameWork
;
// start class TTextEditorVisitor
procedure TTextEditorVisitor.DoIt;
var
l_SI : TevStyleInterface;
l_Size : Integer;
l_NewSize : Integer;
l_MaxHeight : Integer;
begin
l_SI := TevStyleInterface.Make;
try
l_NewSize := GetNormalFontSize;
l_Size := l_SI.Font.Size;
try
if (l_NewSize > 0) then
l_SI.Font.Size := l_NewSize;
l_MaxHeight := MaxHeight;
if (l_MaxHeight > 0) then
begin
f_FixedHeight := 0;
f_FixedHeight := FormExtent.Y;
if (f_FixedHeight < 0) then
f_FixedHeight := 300;
while (f_FixedHeight < l_MaxHeight) do
begin
try
VisitText;
except
ToLog('Form height = ' + IntToStr(f_FixedHeight));
raise;
end;//try..except
Inc(f_FixedHeight);
end;//f_FixedHeight < l_MaxHeight
end//l_MaxHeight > 0
else
VisitText;
finally
l_SI.Font.Size := l_Size;
end;//try..finally
finally
FreeAndNil(l_SI);
end;//try..finally
end;//TTextEditorVisitor.DoIt
function TTextEditorVisitor.GetNormalFontSize: Integer;
begin
Result := 0;
end;//TTextEditorVisitor.GetNormalFontSize
function TTextEditorVisitor.MaxHeight: Integer;
begin
Result := 0;
end;//TTextEditorVisitor.MaxHeight
end.
-----------------------------------------
unit TextEditorVisitorPrim;
//
// The library "TestFormsTest"
// Generated from UML model, root element: TestCase::Class Shared Delphi Operations For Tests::TestFormsTest::Everest::TTextEditorVisitorPrim
//
// Test that works with text of the document using the editor but does not change it
//
interface
uses
TextViaEditorProcessorPrim,
PrimTextLoad_Form
;
type
TTextEditorVisitorPrim = {abstract} class(TTextViaEditorProcessorPrim)
{* Test that works with text of the document using the editor but does not change it }
protected
// protected methods
procedure VisitText(const aStr: AnsiString = 'Load');
{* The procedure of processing the text }
procedure DoVisit(aForm: TPrimTextLoadForm); virtual; abstract;
{* Process the text }
function TreatExceptionAsSuccess: Boolean; virtual;
end;//TTextEditorVisitorPrim
implementation
uses
l3Base,
k2OperationContainer,
TestFrameWork
;
// start class TTextEditorVisitorPrim
procedure TTextEditorVisitorPrim.VisitText(const aStr: AnsiString = 'Load');
var
l_F : _FormClass_;
l_Raise : Boolean;
l_DisableLog : Boolean;
begin
l_F := MakeForm;
try
l_F.Show;
l_Raise := TreatExceptionAsSuccess;
l_DisableLog := l_Raise;
if l_DisableLog then
l3System.DisableExceptionToLog;
try
Load(l_F, KPage + '.evd', aStr);
try
try
DoVisit(l_F);
finally
Check(not Tk2OperationContainer.CheckWasExceptionInFreeInOwner);
end;//try..finally
except
if l_Raise then
{l_Raise := false}
Exit
// - so that not to miss the moment when test succeeds
else
raise;
end;//try..except
finally
if l_DisableLog then
l3System.EnableExceptionToLog;
end;//try..finally
Check(not l_Raise, 'The test is not supposed to succeed, because text does not fit in the paper');
finally
l_F.Free;
end;//try..finally
end;//TTextEditorVisitorPrim.VisitText
function TTextEditorVisitorPrim.TreatExceptionAsSuccess: Boolean;
begin
Result := false;
end;//TTextEditorVisitorPrim.TreatExceptionAsSuccess
end.
--------------------------------
unit TextViaEditorProcessorPrim;
//
// The library "TestFormsTest"
// Generated from UML model, root element: TestCase::Class Shared Delphi Operations For Tests::TestFormsTest::Everest::TTextViaEditorProcessorPrim
//
// The processor of the text through the editor
//
interface
uses
nevTools
,
VCMBaseTest
,
PrimTextLoad_Form,
Types
;
type
_FormClass_ = TPrimTextLoadForm;
_FormProducer_Parent_ = TVCMBaseTest;
{$Include FormProducer.imp.pas}
TTextViaEditorProcessorPrim = {abstract} class(_FormProducer_)
{* The processor of the text through the editor }
private
// private fields
f_ScrollCount : Integer;
{* Field for property ScrollCount}
protected
// overridden protected methods
procedure FormMade(const aForm: _FormClass_); override;
function MakeFormClass: FormClassRef; override;
protected
// protected methods
procedure Load(aForm: TPrimTextLoadForm;
const aFileName: AnsiString;
const aStr: AnsiString = 'Load');
{* Loads the document to the editor of the specified form }
procedure Save(aForm: TPrimTextLoadForm); virtual;
{* Saves the text from the editor in a standard output file }
procedure Scroll(aForm: TPrimTextLoadForm;
const aSubName: AnsiString);
procedure ScrollBack(aForm: TPrimTextLoadForm;
const aSubName: AnsiString);
{* Scrolls the text in the reverse order }
procedure ScrollByWeel(aForm: TPrimTextLoadForm;
aCount: Integer;
aGoTop: Boolean = True);
{* Scrolls with the mouse wheel for a specified number of times }
procedure ScrollByLine(aForm: TPrimTextLoadForm;
aCount: Integer;
aUp: Boolean;
aFromBottom: Boolean);
{* Scroll line-by-line up and down for a specified number of times }
procedure GotoDocumentBottom(aForm: TPrimTextLoadForm);
{* Go to the end of the document }
procedure PageUp(aForm: TPrimTextLoadForm);
{* Go one page up }
procedure PageDown(aForm: TPrimTextLoadForm);
function ScrollByPage: Boolean; virtual;
function WebStyle: Boolean; virtual;
function SendKey: Boolean; virtual;
{* Managing the editor dialog using key sending instead of calling the editor methods }
procedure CheckTopAnchor(const aView: InevInputView); virtual;
{* check anchor of the beginning of drawing after finishing the scrolling }
function F1Like: Boolean; virtual;
function QFLike: Boolean; virtual;
{* Create editor form to work with КЗ. }
procedure DoBeforeLoad(aForm: TPrimTextLoadForm); virtual;
{* Operations before document loading }
function AllowMultiSelect: Boolean; virtual;
{* Allow multiselection. }
function WithBaseSearch: Boolean; virtual;
{* The form with a base search line. }
public
// public properties
property ScrollCount: Integer
read f_ScrollCount;
{* Number of scrollings to the end of the document }
end;//TTextViaEditorProcessorPrim
implementation
uses
Document_Const,
k2OperationContainer,
evdNativeWriter,
l3Filer,
SysUtils,
evOp,
l3InternalInterfaces,
Forms,
Windows,
evCustomEditorWindow,
Messages,
TextLoad_Form,
evdSchema,
F1LikeTextLoad_Form,
QFLikeTextLoad_Form,
F1LikeFormWithBS_Form,
TestFrameWork,
vcmBase,
l3Base
;
{$Include FormProducer.imp.pas}
// start class TTextViaEditorProcessorPrim
procedure TTextViaEditorProcessorPrim.Load(aForm: TPrimTextLoadForm;
const aFileName: AnsiString;
const aStr: AnsiString = 'Load');
begin
with aForm do
begin
DoBeforeLoad(aForm);
LoadManager.FileName := FileFromCurrent(aFileName);
StartTimer;
try
LoadManager.Load(TextSource, k2_idDocument);
finally
StopTimer(aStr);
end;//try..finally
end;//with aForm
Check(not Tk2OperationContainer.CheckWasExceptionInFreeInOwner);
end;//TTextViaEditorProcessorPrim.Load
procedure TTextViaEditorProcessorPrim.Save(aForm: TPrimTextLoadForm);
var
l_Writer : TevdNativeWriter;
l_Filer : Tl3CustomFiler;
begin
l_Writer := TevdNativeWriter.Create;
try
l_Writer.Binary := false;
l_Filer := FilerForOutput;
try
l_Writer.Filer := l_Filer;
finally
FreeAndNil(l_Filer);
end;//try..finally
aForm.TextSource.Save(l_Writer);
finally
FreeAndNil(l_Writer);
end;//try..finally
end;//TTextViaEditorProcessorPrim.Save
procedure TTextViaEditorProcessorPrim.Scroll(aForm: TPrimTextLoadForm;
const aSubName: AnsiString);
var
l_Now : Cardinal;
begin
with aForm do
begin
if (Text.View <> nil) then
begin
Il3CommandTarget(Text).ProcessCommand(ev_ocTopLeft, true, 1);
l_Now := StartTimer;
try
try
f_ScrollCount := 0;
while not Text.View.IsDocumentTailVisible do
begin
if ScrollByPage then
begin
if SendKey then
begin
PostMessage(Text.Handle, $100, $22, $1510001); //Hold down PgDown
Inc(f_ScrollCount);
end
else
if Il3CommandTarget(Text).ProcessCommand(ev_ocPageDown, true, 1) then
Inc(f_ScrollCount)
else
break;
end//ScrollByPage
else
Text.Perform(WM_VScroll, SB_WheelDown, 0);
Application.ProcessMessages;
CheckTimeout(l_Now, 20 * 60 * 1000);
if ShouldStop then Break;
end;//while not Text.View.IsDocumentTailVisible
finally
if ScrollByPage then
if SendKey then
PostMessage(Text.Handle, $101, $22, $1510001); //Pressing up PgDown
end;//try..finally
Application.ProcessMessages;
finally
StopTimer('Scroll', aSubName);
end;//try..finally
end;//Text.View <> nil
end;//with aForm
end;//TTextViaEditorProcessorPrim.Scroll
procedure TTextViaEditorProcessorPrim.ScrollBack(aForm: TPrimTextLoadForm;
const aSubName: AnsiString);
var
l_Now : Cardinal;
begin
with aForm do
begin
if (Text.View <> nil) then
begin
GotoDocumentBottom(aForm);
l_Now := StartTimer;
try
while not Text.View.TopAnchor.AtStart{.IsDocumentTailVisible} do
begin
// PostMessage(Text.Handle, $100, $22, $1510001); //Pressing down PgDown
if ScrollByPage then
begin
if not Il3CommandTarget(Text).ProcessCommand(ev_ocPageUp, true, 1) then
break;
end//ScrollByPage
else
Text.Perform(WM_VScroll, SB_WheelUp, 0);
Application.ProcessMessages;
CheckTimeout(l_Now, 20 * 60 * 1000);
if ShouldStop then Break;
end;//while not Text.View.IsDocumentTailVisible
// PostMessage(Text.Handle, $101, $22, $1510001); //Pressing up PgDown
Application.ProcessMessages;
finally
StopTimer('ScrollBack', aSubName);
end;//try..finally
end;//Text.View <> nil
end;//with aForm
end;//TTextViaEditorProcessorPrim.ScrollBack
procedure TTextViaEditorProcessorPrim.ScrollByWeel(aForm: TPrimTextLoadForm;
aCount: Integer;
aGoTop: Boolean = True);
var
i: Integer;
begin
with aForm do
begin
if aGoTop then
Il3CommandTarget(Text).ProcessCommand(ev_ocTopLeft, true, 1);
for i := 0 to aCount - 1 do
begin
Text.Perform(WM_VScroll, SB_WheelDown, 0);
Application.ProcessMessages;
if ShouldStop then Break;
end;
end; // with aForm do
end;//TTextViaEditorProcessorPrim.ScrollByWeel
procedure TTextViaEditorProcessorPrim.ScrollByLine(aForm: TPrimTextLoadForm;
aCount: Integer;
aUp: Boolean;
aFromBottom: Boolean);
var
i: Integer;
begin
with aForm do
begin
if aFromBottom and aUp then
GotoDocumentBottom(aForm);
if aCount > 0 then
if aUp then
for i := 0 to aCount - 1 do
begin
aForm.Text.View.Scroller[True].LineUp(1);
Application.ProcessMessages;
if ShouldStop then Break;
end // for i := 0 to aCount - 1 do
else
for i := 0 to aCount - 1 do
begin
aForm.Text.View.Scroller[True].LineDown(1);
Application.ProcessMessages;
if ShouldStop then Break;
end // for i := 0 to aCount - 1 do
else
if aCount < 0 then
if aUp then
while not Text.View.TopAnchor.AtStart do
begin
aForm.Text.View.Scroller[True].LineUp(1);
Application.ProcessMessages;
CheckTopAnchor(aForm.Text.View);
if ShouldStop then Break;
end
else
while not Text.View.IsDocumentTailVisible do
begin
aForm.Text.View.Scroller[True].LineDown(1);
Application.ProcessMessages;
CheckTopAnchor(aForm.Text.View);
if ShouldStop then Break;
end;
end; // with aForm do
end;//TTextViaEditorProcessorPrim.ScrollByLine
procedure TTextViaEditorProcessorPrim.GotoDocumentBottom(aForm: TPrimTextLoadForm);
begin
if QFLike then
// - otherwise the editor КЗ processes it in his own way
aForm.Text.View.Caret.Bottom
else
Il3CommandTarget(aForm.Text).ProcessCommand(ev_ocBottomRight, true, 1);
end;//TTextViaEditorProcessorPrim.GotoDocumentBottom
procedure TTextViaEditorProcessorPrim.PageUp(aForm: TPrimTextLoadForm);
begin
Il3CommandTarget(aForm.Text).ProcessCommand(ev_ocPageUp, true, 1);
end;//TTextViaEditorProcessorPrim.PageUp
procedure TTextViaEditorProcessorPrim.PageDown(aForm: TPrimTextLoadForm);
begin
Il3CommandTarget(aForm.Text).ProcessCommand(ev_ocPageDown, true, 1);
end;//TTextViaEditorProcessorPrim.PageDown
function TTextViaEditorProcessorPrim.ScrollByPage: Boolean;
begin
Result := true;
end;//TTextViaEditorProcessorPrim.ScrollByPage
function TTextViaEditorProcessorPrim.WebStyle: Boolean;
begin
Result := true;
end;//TTextViaEditorProcessorPrim.WebStyle
function TTextViaEditorProcessorPrim.SendKey: Boolean;
begin
Result := false;
end;//TTextViaEditorProcessorPrim.SendKey
procedure TTextViaEditorProcessorPrim.CheckTopAnchor(const aView: InevInputView);
begin
end;//TTextViaEditorProcessorPrim.CheckTopAnchor
function TTextViaEditorProcessorPrim.F1Like: Boolean;
begin
Result := false;
end;//TTextViaEditorProcessorPrim.F1Like
function TTextViaEditorProcessorPrim.QFLike: Boolean;
begin
Result := false;
end;//TTextViaEditorProcessorPrim.QFLike
procedure TTextViaEditorProcessorPrim.DoBeforeLoad(aForm: TPrimTextLoadForm);
begin
end;//TTextViaEditorProcessorPrim.DoBeforeLoad
function TTextViaEditorProcessorPrim.AllowMultiSelect: Boolean;
begin
Result := True;
end;//TTextViaEditorProcessorPrim.AllowMultiSelect
function TTextViaEditorProcessorPrim.WithBaseSearch: Boolean;
begin
Result := False;
end;//TTextViaEditorProcessorPrim.WithBaseSearch
procedure TTextViaEditorProcessorPrim.FormMade(const aForm: _FormClass_);
begin
inherited;
aForm.Text.WebStyle := WebStyle;
aForm.Text.AllowMultiSelect := AllowMultiSelect;
end;//TTextViaEditorProcessorPrim.FormMade
function TTextViaEditorProcessorPrim.MakeFormClass: FormClassRef;
begin
if WithBaseSearch then
Result := TF1LikeFormWithBSForm
else
if QFLike then
Result := TQFLikeTextLoadForm
else
if F1Like then
Result := TF1LikeTextLoadForm
else
Result := TTextLoadForm;
end;//TTextViaEditorProcessorPrim.MakeFormClass
end.
Комментариев нет:
Отправить комментарий