WPF:TestApiを用いてユーザ入力をシミュレートする

11680 ワード

参考資料:
(1). WPF:FrameworkElementを自動的にクリック
(2). TestApi - a library of Test APIs
(3). Introduction to TestApi – Part 1: Input Injection APIs
 
1.ユーザー入力をシミュレートする5つの方法:
(A)UI elementを直接呼び出す方法、例えば:Button.IsPressed
(B)利用可能なインターフェース(UIA,MSAA,etc.)、例えば:AutomationElement
(C)下部入力シミュレーションを使用して、オペレーティングシステムに関連して、例えば:WindowsのSendInput Win32 APIRaw Input Win32 API
(D)デバイス駆動シミュレーションの使用
(E)ロボットを用いて人間の操作をシミュレートし、例えばキーボードを叩く
方法Aはframeworkレベルであり、WPFに対してのみ有効であり、Winformに対しては無効である.
方法Bはframeworkよりもレベルが低いが、いくつかのframeworkが必要とする利用可能なポート実装方式が異なるため、多くの制限がある.
方法CとDはオペレーティングシステムレベルであり、DはCより実現しにくい.
方法Eは一般的に使われている方法です(アメリカだけだと思いますが、汗)、代価が高くてスピードが遅いです.
TestApiは、B方式がAutomationUtilitiesクラス、C方式がMouseとKeyboardの2つのクラスで実装される最も一般的なBとCの2つの方式を提供しています.
 
2.TestApiシミュレーションを用いた例
例1:WPF WindowWPF Buttonを検索して押す、AutomationUtilitiesとMouseクラスを使用する.

  
    
//
// EXAMPLE #1
// This code below discovers and clicks the Close button in an About
// dialog box, thus dismissing the About dialog box.
//

string aboutDialogName = " About " ;
string closeButtonName = " Close " ;

AutomationElementCollection aboutDialogs
= AutomationUtilities.FindElementsByName(
AutomationElement.RootElement,
aboutDialogName);

AutomationElementCollection closeButtons
= AutomationUtilities.FindElementsByName(
aboutDialogs[
0 ],
closeButtonName);

//
// You can either invoke the discovered control, through its invoke
// pattern...
//

InvokePattern p
=
closeButtons[
0 ].GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
p.Invoke();

//
// ... or you can handle the Mouse directly and click on the control.
//

Mouse.MoveTo(closeButton.GetClickablePoint());
Mouse.Click(MouseButton.Left);

例2:TextBoxを自動的に検索し、その中でタイプし、MouseとKeyboardクラスを使用する

  
    
//
// EXAMPLE #2
// Discover the location of a TextBox with a given name.
//

string textboxName = " ssnInputField " ;

AutomationElement textBox
= AutomationUtilities.FindElementsByName(
AutomationElement.RootElement,
textboxName)[
0 ];

Point textboxLocation
= textbox.GetClickablePoint();

//
// Move the mouse to the textbox, click, then type something
//

Mouse.MoveTo(textboxLocation);
Mouse.Click(MouseButton.Left);

Keyboard.Type(
" Hello world. " );
Keyboard.Press(Key.Shift);
Keyboard.Type(
" hello, capitalized world. " );
Keyboard.Release(Key.Shift);

 
3.後記
TestApiのMouseクラスとKeyboardクラスは、任意のフォームアプリケーションで使用できます.テストフレームワークやテストプロセスに関係なく、ソースコードとドキュメントが提供されています.自分のプロジェクトに統合したり、Dllを直接参照したりすることができます.
なお、TestApiはこのような簡単な方法でUIテストを実現しているが、UIテストは厄介で複雑なことであり、いつでも避けるべきである.一般的に、UIテストを最小限に抑えるために、アプリケーションで多層設計モード(multi-tier)を採用し、UIテストを最小限に抑えるために浅いUIを設計します.
 
4.添付:コントロールの位置を計算する方法
TestApiのGetClickablePoint()メソッドではなく、コントロール要素の位置を個別に計算するには、次の方法を使用します.
1:///
2:///Get mouse move to location
3:///

4:///element
5:///wpf logical pixel offset
6:///screen physical pixel location
7: public static System.Drawing.Point GetMoveToLocation(FrameworkElement element, Point logicalOffset)
8: {
9:   Point mouseLocation = default(Point);
10:  FlowDirection flowDirection = Window.GetWindow(element).FlowDirection;
11: 
12:  //We don't need to convert element location to physical screen pixel because wpf takes care of it.
13:  Point elementLocation = element.PointToScreen(new Point());
14: 
15://We need to convert offset to physical screen pixel since we're pass in wpf logical pixel
16: double physicalXOffset = ConvertToPhysicalPixel(logicalOffset.X);
17: double physicalYOffset = ConvertToPhysicalPixel(logicalOffset.Y);
18: 
19: switch (flowDirection)
20:  {
21:    case FlowDirection.LeftToRight:
22:      mouseLocation = new Point(elementLocation.X + physicalXOffset, elementLocation.Y + physicalYOffset);
23:      break;
24:   case FlowDirection.RightToLeft:
25:   //We need to subtract physical offsetX because the element location starting point is in right most
26:     mouseLocation = new Point(elementLocation.X - physicalXOffset, elementLocation.Y + physicalYOffset);
27:     break;
28:  }
29: 
30:   return new System.Drawing.Point(Convert.ToInt32(mouseLocation.X), Convert.ToInt32(mouseLocation.Y));
31: }
32: 
33:///
34:///WPF has its own pixel system in double value type, and screen pixel includes different DPIs is in int value type.
35:///In 96 dpi, wpf and screen pixels are the same, but other dpi, we need to convert wpf logical pixel to screen physical
36:///pixel by using formula (wpf pixel value * dpi/96.0).
37:///

38:///Logical(WPF) pixel value
39:///Physical(Screen) pixel value
40: public static int ConvertToPhysicalPixel(double logicalPixel)
41: {
42:    return Convert.ToInt32(logicalPixel * GetDpi()/96.0);
43: }
44: 
45:///
46:///Get DPI of the system
47:///

48:///
49: public static float GetDpi()
50: {
51:   using (System.Drawing.Graphics graph = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))
52:   {
53:      if (graph == null)
54:      {
55:        throw new NullReferenceException("Graphics not found");
56:      }
57: 
58:     if (!graph.DpiX.Equals(graph.DpiY))
59:      {
60:        throw new ArithmeticException("DpiX != DpiY");
61:      }
62: 
63:     return graph.DpiX;
64:   }
65: }