approaches to automatically testing visual components richard b. winston cpcug programmers sig nov....

21
Approaches to Approaches to Automatically Testing Automatically Testing Visual Components Visual Components Richard B. Winston Richard B. Winston CPCUG Programmers SIG CPCUG Programmers SIG Nov. 1, 2006 Nov. 1, 2006

Upload: arlene-ford

Post on 19-Jan-2016

219 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

Approaches to Approaches to Automatically Testing Automatically Testing Visual ComponentsVisual Components

Richard B. WinstonRichard B. Winston

CPCUG Programmers SIGCPCUG Programmers SIG

Nov. 1, 2006Nov. 1, 2006

Page 2: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

OutlineOutline

• Review of DUnitReview of DUnit

• Difficulties Using Dunit with Visual Difficulties Using Dunit with Visual ComponentsComponents

• Simulating User Interaction in DUnitSimulating User Interaction in DUnit

• Comparing Appearances of Visual Comparing Appearances of Visual ComponentsComponents

• LimitationsLimitations

Page 3: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

Review of DUnitReview of DUnit

• Testing Framework for DelphiTesting Framework for Delphi

• Open Source: Open Source: http://dunit.sourceforge.net/http://dunit.sourceforge.net/

• Built-in support in BDS 2005 and Built-in support in BDS 2005 and 2006 2006

• Works with Delphi >= 5Works with Delphi >= 5

Page 4: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

DUnit Example: 1DUnit Example: 1

typetype // Test methods for class TRbwParser// Test methods for class TRbwParser TestTRbwParser = class(TTestCase)TestTRbwParser = class(TTestCase) strict privatestrict private FRbwParser: TRbwParser;FRbwParser: TRbwParser; publicpublic procedure SetUp; override;procedure SetUp; override; procedure TearDown; override;procedure TearDown; override; publishedpublished procedure TestClearExpressions;procedure TestClearExpressions; … … end;end;

Page 5: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

DUnit Example: 2DUnit Example: 2

procedure TestTRbwParser.TestClearExpressions;procedure TestTRbwParser.TestClearExpressions;varvar Expression: string;Expression: string;beginbegin if FRbwParser.ExpressionCount = 0 thenif FRbwParser.ExpressionCount = 0 then beginbegin Expression := '1+2';Expression := '1+2'; FRbwParser.Compile(Expression);FRbwParser.Compile(Expression); end;end; Check(FRbwParser.ExpressionCount > 0, 'Error in counting Check(FRbwParser.ExpressionCount > 0, 'Error in counting

expressions');expressions');

FRbwParser.ClearExpressions;FRbwParser.ClearExpressions; Check(FRbwParser.ExpressionCount = 0, 'Error in clearing Check(FRbwParser.ExpressionCount = 0, 'Error in clearing

expressions');expressions');end;end;

Page 6: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

DUnit in ActionDUnit in Action

Page 7: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

Manually Testing User Manually Testing User InteractionInteraction

• ManualManual– Ask the user to do Ask the user to do

somethingsomething– Let the user decide Let the user decide

if the results are if the results are correctcorrect

• ProblemsProblems– SlowSlow– UnreliableUnreliable– Discourages TestingDiscourages Testing

Page 8: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

Automatically Test User Automatically Test User InteractionInteraction

• Simulate Mouse and Keyboard EventsSimulate Mouse and Keyboard Events• Get Screen Captures of the ControlsGet Screen Captures of the Controls• Compare the New Screen Captures to Compare the New Screen Captures to

Old OnesOld Ones• Borland’s private “Zombie” program Borland’s private “Zombie” program

does something similar. See does something similar. See http://www.stevetrefethen.com/files/inhttp://www.stevetrefethen.com/files/index.htmldex.html

Page 9: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

Windows API for Simulating Windows API for Simulating the Mouse and Keyboardthe Mouse and Keyboard

• Mouse_Event( MOUSEEVENTF_LEFTMouse_Event( MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);DOWN, 0, 0, 0, 0);

• Keybd_Event(key, Keybd_Event(key, MapvirtualKey(key, 0), flag, 0)MapvirtualKey(key, 0), flag, 0)

Page 10: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

Simulate Mouse and Simulate Mouse and KeyboardKeyboard

// move the mouse to cell [0,1]// move the mouse to cell [0,1]

APoint := CellCenter(frmGridTest.TestGrid, 0,1);APoint := CellCenter(frmGridTest.TestGrid, 0,1);

MoveMouseToPosition(APoint);MoveMouseToPosition(APoint);

// double click on the cell to start editing text.// double click on the cell to start editing text.

DoubleClick;DoubleClick;

// enter some text.// enter some text.

SimultateText('abcd efgh ijkl');SimultateText('abcd efgh ijkl');

Page 11: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

CellCenterCellCenter

function CellCenter(Const Grid: TCustomDrawGrid; function CellCenter(Const Grid: TCustomDrawGrid; Const ACol, ARow: integer): TPoint;Const ACol, ARow: integer): TPoint;

varvar ARect: TRect;ARect: TRect;beginbegin ARect := Grid.CellRect(ACol,ARow);ARect := Grid.CellRect(ACol,ARow); result.X := (ARect.Left + ARect.Right) div 2;result.X := (ARect.Left + ARect.Right) div 2; result.Y := (ARect.Top + ARect.Bottom) div 2;result.Y := (ARect.Top + ARect.Bottom) div 2; result := Grid.ClientToScreen(result);result := Grid.ClientToScreen(result);end;end;

Page 12: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

MoveMouseToPositionMoveMouseToPosition

Procedure Procedure MoveMouseToPosition(Position: MoveMouseToPosition(Position: TPoint);TPoint);

BeginBegin

Mouse.CursorPos := Position;Mouse.CursorPos := Position;

Application.ProcessMessages;Application.ProcessMessages;

end;end;

Page 13: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

MouseClickMouseClick

procedure MouseGoesDown;procedure MouseGoesDown;beginbegin

Mouse_Event( MOUSEEVENTF_Mouse_Event( MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);LEFTDOWN, 0, 0, 0, 0);

end;end;

procedure MouseGoesUp;procedure MouseGoesUp;beginbegin

Mouse_Event( MOUSEEVENTF_LMouse_Event( MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);EFTUP, 0, 0, 0, 0);

end;end;

procedure MouseClick;procedure MouseClick;beginbegin MouseGoesDown;MouseGoesDown; MouseGoesUp;MouseGoesUp;end;end;

procedure DoubleClick;procedure DoubleClick;beginbegin MouseClick;MouseClick; MouseClick;MouseClick;end;end;

Page 14: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

DragMouseDragMouse

procedure DragMouse(Start, Stop: TPoint);procedure DragMouse(Start, Stop: TPoint);ConstConst TenthSecond = 100; // 100 milliseconds - 1/10 second. This value works for me.TenthSecond = 100; // 100 milliseconds - 1/10 second. This value works for me.beginbegin MoveMouseToPosition(Start);MoveMouseToPosition(Start); MouseGoesDown;MouseGoesDown; trytry MoveMouseToPosition(Stop);MoveMouseToPosition(Stop); // To generate a mouse move event, enough time must elapse.// To generate a mouse move event, enough time must elapse. // Empirically 1/20'th of a second is enough.// Empirically 1/20'th of a second is enough. Sleep(TenthSecond);Sleep(TenthSecond); Application.ProcessMessages;Application.ProcessMessages; finallyfinally MouseGoesUp;MouseGoesUp; Application.ProcessMessages;Application.ProcessMessages; end;end;end;end;

Page 15: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

SimultateTextSimultateText

Procedure SimultateText(const AString: Procedure SimultateText(const AString: string);string);

varvar CharIndex: integer;CharIndex: integer; AChar: Char;AChar: Char; KeyCode: TKeyCode;KeyCode: TKeyCode; Shift: TShiftState;Shift: TShiftState;beginbegin for CharIndex := 1 to Length(AString) dofor CharIndex := 1 to Length(AString) do beginbegin AChar := AString[CharIndex];AChar := AString[CharIndex];

KeyCode.LongKeyCode := KeyCode.LongKeyCode := VkKeyScan(AChar);VkKeyScan(AChar);

Shift := [];Shift := [];

if (KeyCode.Shift and 1) <> 0 thenif (KeyCode.Shift and 1) <> 0 then beginbegin Include(Shift, ssShift);Include(Shift, ssShift); end;end; if (KeyCode.Shift and 2) <> 0 thenif (KeyCode.Shift and 2) <> 0 then beginbegin Include(Shift, ssCtrl);Include(Shift, ssCtrl); end;end; if (KeyCode.Shift and 4) <> 0 thenif (KeyCode.Shift and 4) <> 0 then beginbegin Include(Shift, ssAlt);Include(Shift, ssAlt); end;end;

PostKeyEx32(KeyCode.KeyCode, Shift, PostKeyEx32(KeyCode.KeyCode, Shift, False);False);

end;end;end;end;

Page 16: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

PostKeyEx32PostKeyEx32// from // from

http://delphi.about.com/od/adptips200http://delphi.about.com/od/adptips2004/a/bltip0604_4.htm4/a/bltip0604_4.htm

procedure PostKeyEx32(key: Word; const procedure PostKeyEx32(key: Word; const shift: TShiftState; specialkey: Boolean) shift: TShiftState; specialkey: Boolean) ;;

typetype TShiftKeyInfo = recordTShiftKeyInfo = record shift: Byte ;shift: Byte ; vkey: Byte ;vkey: Byte ; end;end; ByteSet = set of 0..7 ;ByteSet = set of 0..7 ;constconst shiftkeys: array [1..3] of TShiftKeyInfo =shiftkeys: array [1..3] of TShiftKeyInfo = ((shift: Ord(ssCtrl) ; vkey: ((shift: Ord(ssCtrl) ; vkey:

VK_CONTROL),VK_CONTROL), (shift: Ord(ssShift) ; vkey: VK_SHIFT),(shift: Ord(ssShift) ; vkey: VK_SHIFT), (shift: Ord(ssAlt) ; vkey: VK_MENU)) ;(shift: Ord(ssAlt) ; vkey: VK_MENU)) ;varvar flag: DWORD;flag: DWORD; bShift: ByteSet absolute shift;bShift: ByteSet absolute shift; j: Integer;j: Integer;

beginbegin for j := 1 to 3 dofor j := 1 to 3 do beginbegin if shiftkeys[j].shift in bShift thenif shiftkeys[j].shift in bShift then keybd_event(shiftkeys[j].vkey, keybd_event(shiftkeys[j].vkey, MapVirtualKey(shiftkeys[j].vkey, 0), 0, 0) ;MapVirtualKey(shiftkeys[j].vkey, 0), 0, 0) ; end;end; if specialkey thenif specialkey then flag := KEYEVENTF_EXTENDEDKEYflag := KEYEVENTF_EXTENDEDKEY elseelse flag := 0;flag := 0;

keybd_event(key, MapvirtualKey(key, 0), flag, 0) ;keybd_event(key, MapvirtualKey(key, 0), flag, 0) ; flag := flag or KEYEVENTF_KEYUP;flag := flag or KEYEVENTF_KEYUP; keybd_event(key, MapvirtualKey(key, 0), flag, 0) ;keybd_event(key, MapvirtualKey(key, 0), flag, 0) ;

for j := 3 downto 1 dofor j := 3 downto 1 do beginbegin if shiftkeys[j].shift in bShift thenif shiftkeys[j].shift in bShift then keybd_event(shiftkeys[j].vkey, keybd_event(shiftkeys[j].vkey, MapVirtualKey(shiftkeys[j].vkey, 0), MapVirtualKey(shiftkeys[j].vkey, 0), KEYEVENTF_KEYUP, 0) ;KEYEVENTF_KEYUP, 0) ; end;end;end;end;

Page 17: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

Comparing Appearances of Comparing Appearances of Visual ComponentsVisual Components PaintControlToBitMap(frmGridTest.TestGrid, PaintControlToBitMap(frmGridTest.TestGrid, TestBitMap, True);TestBitMap, True); frmGridTest.Hide;frmGridTest.Hide;

BitmapKey := BitmapKey := RbwDataGridAutoWordWrapAdjustRowHeight;RbwDataGridAutoWordWrapAdjustRowHeight; UserPrompt := UserPrompt := 'Has the text in the string and boolean cells word 'Has the text in the string and boolean cells word

wrapped 'wrapped ' + 'and have the rows expanded to accommodate the + 'and have the rows expanded to accommodate the

text?';text?'; Result := CompareAndUpdateBitmap(BitmapKey, Result := CompareAndUpdateBitmap(BitmapKey,

UserPrompt, UserPrompt, TestBitMap);TestBitMap);

Page 18: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

PaintControlToBitMapPaintControlToBitMap

Procedure PaintControlToBitMap(Control: TControl;Procedure PaintControlToBitMap(Control: TControl; BitMap: TBitMap; IncludeCursor: boolean);BitMap: TBitMap; IncludeCursor: boolean);beginbegin// Set the size of the bitmap that will hold the image of Control// Set the size of the bitmap that will hold the image of Control BitMap.Width := Control.Width;BitMap.Width := Control.Width; BitMap.Height := Control.Height;BitMap.Height := Control.Height;

if Control is TWinControl thenif Control is TWinControl then beginbegin TWinControl(Control).PaintTo(BitMap.Canvas, 0, 0);TWinControl(Control).PaintTo(BitMap.Canvas, 0, 0); endend else if Control is TGraphicControl thenelse if Control is TGraphicControl then beginbegin // Get a screen capture of the form containing the TGraphicControl// Get a screen capture of the form containing the TGraphicControl // and copy only the portion of that screen capture that contains// and copy only the portion of that screen capture that contains // the TGraphicControl into BitMap.// the TGraphicControl into BitMap.

Page 19: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

CompareAndUpdateBitmapCompareAndUpdateBitmap

function function CompareAndUpdateBitmap(const CompareAndUpdateBitmap(const BitmapKey, UserPrompt: string;BitmapKey, UserPrompt: string;

TestBitMap: TBitmap): Boolean;TestBitMap: TBitmap): Boolean;varvar PredefinedBitMap: TBitmap;PredefinedBitMap: TBitmap;beginbegin PredefinedBitMap := PredefinedBitMap :=

FBitmapStorage.BitMapCollection.FBitmapStorage.BitMapCollection.Find(BitmapKey);Find(BitmapKey);

result := result := CompareBitMaps(TestBitMap, CompareBitMaps(TestBitMap, PredefinedBitMap);PredefinedBitMap);

if not result and (TestMethod = if not result and (TestMethod = tmScripted) thentmScripted) then

beginbegin result := result :=

UserCompareBitMap(PredefinedBiUserCompareBitMap(PredefinedBitMap, TestBitMap, UserPrompt);tMap, TestBitMap, UserPrompt);

if result thenif result then beginbegin if PredefinedBitMap = nil thenif PredefinedBitMap = nil then beginbegin FBitmapStorage.FBitmapStorage.

BitMapCollection.Store(BitmapKey,BitMapCollection.Store(BitmapKey, TestBitMap);TestBitMap); endend elseelse beginbegin PredefinedBitMap.Assign(PredefinedBitMap.Assign( TestBitMap);TestBitMap); end;end; end;end; end;end;end;end;

Page 20: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

LimitationsLimitations

• Someone has to check the Someone has to check the appearance of a control manually at appearance of a control manually at least once.least once.

• There can be no manual use of the There can be no manual use of the mouse or keyboard during a test.mouse or keyboard during a test.

• Changes to the computer outside of Changes to the computer outside of the control of the test, such as the control of the test, such as changing from small to large fonts, changing from small to large fonts, will affect the bitmap comparisons.will affect the bitmap comparisons.

Page 21: Approaches to Automatically Testing Visual Components Richard B. Winston CPCUG Programmers SIG Nov. 1, 2006

SummarySummary

• DUnit can be used to test visual DUnit can be used to test visual controls.controls.

• Manual user interaction during tests Manual user interaction during tests can be reduced but not entirely can be reduced but not entirely eliminated.eliminated.

• The tests are fragile – They are easily The tests are fragile – They are easily affected by changes to the computer affected by changes to the computer outside the test.outside the test.