'automation'에 해당되는 글 3건
- 2009.01.09 실버라이트에서의 UI 자동화 - Part 2 (쉬운 방법)
- 2009.01.09 [번역] 실버라이트에서의 UI Automation - 사용자 인터랙션 시뮬레이션
- 2009.01.05 UI Automation에 관련된 글. 1
<TextBox AutomationProperties.AutomationId="SearchTextBox" x:Name="SearchText" KeyDown="CheckKey" />
Identification
ClassName: ""
ControlType: "ControlType.Custom"
Culture: "(null)"
AutomationId: "SearchTextBox"
LocalizedControlType: "custom"
Name: ""
ProcessId: "2808 (iexplore)"
RuntimeId: "42 197822 3"
IsPassword: "False"
IsControlElement: "True"
IsContentElement: "True"
Visibility
BoundingRectangle: "(240, 327, 300, 23)"
ClickablePoint: "390,338"
IsOffscreen: "False"
public static class ExtensionMethods
{
public static System.Drawing.Point ToDrawingPoint(this System.Windows.Point windowsPoint)
{
return new System.Drawing.Point
{
X = Convert.ToInt32(windowsPoint.X),
Y = Convert.ToInt32(windowsPoint.Y)
};
}
}
public static class Mouse
{
private const UInt32 MouseEventLeftDown = 0x0002;
private const UInt32 MouseEventLeftUp = 0x0004;
[DllImport("user32.dll")]
private static extern void mouse_event(UInt32 dwFlags, UInt32 dx, UInt32 dy, UInt32 dwData, IntPtr dwExtraInfo);
public static void Click()
{
mouse_event(MouseEventLeftDown, 0, 0, 0, IntPtr.Zero);
mouse_event(MouseEventLeftUp, 0, 0, 0, IntPtr.Zero);
}
}
[TestMethod]
public void TestMethod1()
{
// Assumes an existing Internet Explorer process is running and pointed at your Silverlight app
Process process = System.Diagnostics.Process.GetProcessesByName("iexplore").First();
AutomationElement browserInstance = System.Windows.Automation.AutomationElement.FromHandle(process.MainWindowHandle);
Thread.Sleep(1000);
TreeWalker tw = new TreeWalker(new PropertyCondition(AutomationElement.AutomationIdProperty, "SearchTextBox"));
AutomationElement searchBox = tw.GetFirstChild(browserInstance);
Thread.Sleep(1000);
System.Windows.Point uiaPoint;
if (searchBox.TryGetClickablePoint(out uiaPoint))
{
Cursor.Position = uiaPoint.ToDrawingPoint();
Mouse.Click();
SendKeys.SendWait("Hello, world!");
}
else
{
Assert.Fail();
}
}
만약 동적으로 코드에서 컨트롤을 생성한다면 어떻게 해야 할까요? AutomationProperties에 정적 메소드를 사용하여 AutomationId 프로퍼티를 아래와 같이 설정할 수 있습니다.
ListBoxItem myDynamicListBoxItem = new ListBoxItem { Content = "Hello, world!" };
AutomationProperties.SetAutomationId(myDynamicListBoxItem, "myDynamicListBoxAutomationId");
<appSettings>
<add key="SendKeys" value="SendInput"/>
</appSettings>
UIAutomation에 대한 두번째 번역이 끝났습니다. 사실 UIAutomation은 테스트 자동화보다는 Accessibility에 초점이 맞혀진 기술이죠. 미국의 경우에는 이미 웹페이지에 대한 접근성에 대한 법률이 재정되었다고 하니 앞으로 이런 부분도 신경을 써야 하지 않을까 싶습니다.
프로젝트를 진행하면서 반복되는 리뷸드 그리고 반복되는 타이핑 그리고 반복되는 디버깅을 얼마나 많이 경험했는지 모릅니다. 프로젝트 내부에 아주 조그만한 버그를 잡기 위해 코드를 조금 고치고 프로젝트의 여러 실행단계(로그인 프로세스등등..)를 거쳐서 정말 테스트 하고 싶은 부분을 테스트 한 후 다시 돌아와 디버깅을 해야 하는 경우.. 아무리 앞의 단계를 단순히 한다고해도 정말 짜증나느 일이었죠. 기본적으로 UnitTest화 하여 분리하여 테스트 하는 것이 맞지만 프로젝트를 진행하다보면 그렇게 똑 부러지게 분리되지 않는 경우도 많죠... 그런 경우 이런 테스트 자동화를 통해 어느정도 해결할 수 있지 않을까 합니다.
물론 Unit Test 도 이런 UI 테스트 자동화도 추가적인 코드가 들어가고 자칫 번잡해질 수도 있지만 규모가 큰 프로젝트일 수록 복잡한 프로젝트일 수록 미리미리 이런 것들을 준비해놓는 것이 프로젝트 막판에 패닉상태로 빠지지 않는 지름길이 아닐까 합니다.
Unit Test를 쓰고 안쓰고 UI 테스트 자동화를 하고 안하고는 전적으로 개발자 본인이나 프로젝트 매니저가 선택할 일이지만 일단 사용하려고 하였을 때 제가 번역한 글이 조금이나마 도움이 되길 바랍니다.
그럼.. 마지막으로 제가 이 글을 번역하면서 따라한 테스트 프로젝트를 다시 한번 추가 시켜 놓도록 하겠습니다.
프로젝트를 실행해보는 방법은 먼저 웹프로젝트를 실행시킨뒤 Test를 실행시키시면 됩니다. 그럼..
- smile -
이 글은 다음 링크의 글을 번역한 글입니다.
http://blogs.msdn.com/gisenberg/archive/2008/07/12/ui-automation-in-silverlight-simulating-user-interactions.aspx
----------------------------------------------------------------------------------------------------
최근에 저는 한 임시 그룹에서 실버라이트 리치 인터넷 에플리케이션(RIAs)의 자동화에 대한 일을 맡았습니다. 공중에 발표된 몇가지 툴들은 이 점에서 제한된 도움만을 제공해주고 있습니다. 예를 들면 실버라이트는 컨트롤을 제작 중 Unit Test를 사용할 수 있습니다. 다음 링크에서 좀 더 자세한 것을 알 수 있습니다.
http://www.jeff.wilcox.name/2008/03/31/silverlight2-unit-testing/
불행하게도 저의 요구사항은 자동화 시나리오를 가능하게 하는 것입니다. 우리는 RIA의 안 밖에서 마우스를 움직이거나, 어떤 것을 클릭하거나, 제 3자의 인증공급자로 간다거나, 어떤 키들을 입력하는 등의 몇몇 사용자 흐름을 시뮬레이션 해야만 합니다.
실버라이트에서의 UI 자동화는 여기선 뜨겁게 떠오르는 주제가 되었습니다. 실버라이트 Beta2의 발표와 함께 우리는 갈만한 셋길을 발견하기 시작했습니다. 보다 정확히, 우리는 UI자동화를 하는 WPF의 방법을 조금씩 볼 수 있게 되었습니다. 여러분이 만약 이 글을 같이 따라오기 원한다면 UISpy를 설치하셔야 할 것입니다.(http://blogs.msdn.com/windowssdk/archive/2008/02/18/where-is-uispy-exe.aspx).
마이크로소프트 UI 자동화 어셈블리는 .Net 프레임워크 3.0 과 함께 릴리즈 되었습니다. 전통적으로 우리는 실버라이트 에플리케이션와 함께 아주 먼곳에 있지 않은 Microsoft Active Accessibility(MSAA)와 작동하는 다양한 COM 랩퍼(wrappers)를 가지고 있습니다. 만약 WPF에서 UI 자동화를 조금이라도 해보았다면 앞으로 편하게 이해하실 수 있을 겁니다. 그래서 더 깊게 고려하지 않고 바로 UIA 작업을 시작해보도록 하겠습니다.
첫번째로 Visual Studio 2008 에서 새로운 테스트 프로젝트를 시작합니다. 실버라이트에서 UI 자동화 에플리케이션을 시작하기위해 필요한 UIA 네임스페이스는 System.Windows.Automation과 System.Windows.Automation.Providers 입니다. 그리고 관련된 부분을 얻기 위해 닷넷 3.0 이상에 포함된 다음 어셈블리들을 참조로 추가 해야만합니다.
- UIAutomationProvider.dll
- UIAutomationClient.dll
- UIAutomationClientsideProviders.dll
- UIAutomationTypes.dll
실버라이트 커스텀 컨트롤의 자동화가 필요하다고 합시다. 컨트롤의 접근가능한 기능들을 조정하기 위한 Peer Type을 반환해주기 위해 (Control 클래스로 부터) OnCreateAutomationPeer 메소드를 오버라이드 해야만 합니다. 접근가능한 기능(?)들은 에플리케이션을 자동화할 수 있게 하는 Key가 될 것이기 때문에 이 작업은 매우 중요합니다.
텍스트 박스와 검색버튼으로 이루어진 가상의 검색 컨트롤을 가정합니다.
public partial class SearchBar : Control
{
...
public SearchBar()
{
this.GotFocus += (sender, args)
=>
{
this.SearchText.Focus();
};
InitializeComponent();
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new SearchBarAutomationPeer(this);
}
}
여기서 Override한 OnCreateAutomationPeer는 접근 가능 기능들을 위한 컨트롤 트리를 감시하는 것(결론적으로 automation 함수들)에 의해 불려지게 될 것입니다. Peer객체는 접근성이 필요한 것에 밀착하는 의미로(?) 컨트롤의 조합을 반환할 책임을 지게됩니다.( The Peer object will be responsible for returning your combination of controls in a manner that is coherent to anything that needs accessibility. )
또 여기서 이 Control의 GotFocus 핸들러를 컨트롤의 default .Focus() 동작에 설정하기 위해 엮어두었습니다.
다음으로 SearchBarAutomationPeer 클래스의 구현 부분을 봅시다.
public class SearchBarAutomationPeer : FrameworkElementAutomationPeer, IValueProvider
{
public SearchBarAutomationPeer(SearchBar searchBar) : base(searchBar)
{
}
Peer 클래스는 작업하는데 필요할 모든 메소드를 제공하는 FrameworkElementAutomationPeer 로 부터 상속 받아야 합니다. 그리고 이 컨트롤의 구성요소중 TextBox와의 인터랙션을 위해 IValueProvider 와 맵핑이 필요합니다. 다음 링크에서 Provider 인터페이스와 개개의 구성요소와의 맵핑에 관해 더 알 수 있습니다.
http://msdn.microsoft.com/en-us/library/system.windows.automation.provider.aspx
컨트롤 트리에서 우리 컨트롤을 찾기 위해선 컨트롤에 클래스 이름과 접근 가능한 구별자(accessibility identifier)를 주는 것이 필요합니다. 이것을 하기 위해서는 FrameworkElementAutomationPeer의 GetAutomationIdCore() 와 GetClassNameCore() 함수를 오버라이드 해야만 합니다.
protected override string GetAutomationIdCore()
{
return "SearchBar"; // You're going to want to make this unique. ;)
}
protected override string GetClassNameCore()
{
return "SearchBar";
}
protected override bool IsKeyboardFocusableCore()
{
return true;
}
IsKeyboardFocusableCore 는 꼭 override해야만 함수로 만약 이 함수가 없다면 컨트롤의 SetFocus() 함수의 호출이 실패하게 될 것입니다. 이제 Provider 인터페이스의 구현에 대해서도 생각해봐야 합니다. 생성자에서 건네 받은 SearchBar는 base.Owner 프로퍼티를 통해 얻어 올 수 있습니다. base.Owner로 부터 SearchBar를 매번 캐스팅 하는 단조로움을 피하기 위해 프로퍼티를 하나 추가할 것입니다.
public SearchBar SearchBar
{
get
{
return (SearchBar)base.Owner;
}
}
#region IValueProvider Members
public bool IsReadOnly
{
get
{
return this.SearchBar.SearchText.IsReadOnly;
}
}
public void SetValue(string value)
{
this.SearchBar.SearchText.Text = value;
}
public string Value
{
get
{
return this.SearchBar.SearchText.Text;
}
}
#endregion
이제 UISpy로 우리 컨트로을 보게 되면 다음과 같이 볼 수 있을 겁니다.
Identification
ClassName: "SearchBar"
ControlType: "ControlType.Custom"
Culture: "(null)"
AutomationId: "SearchBar"
LocalizedControlType: "custom"
Name: "SearchBar"
ProcessId: "2276 (iexplore)"
RuntimeId: "42 197110 6"
IsPassword: "False"
IsControlElement: "True"
IsContentElement: "True"
Visibility
BoundingRectangle: "(356, 286, 949, 36)"
ClickablePoint: "830,304"
IsOffscreen: "False"
ControlPatterns
Value
Value: ""
IsReadOnly: "False"
ControlPatterns 아래 "Vaule" 프로퍼티는 자동적으로 IValueProvider 인터페이스로 인해 우리 컨트롤의 TextBox 의 Vaule와 맵핑됩니다. 깔끔하죠?
이제 이 커스텀 컨트롤의 배관이 가능하게 되었습니다. 이제 TestMethod를 살펴봅시다.
[TestMethod]
public void TestMethod1()
{
Process process = System.Diagnostics.Process.GetProcessesByName("iexplore").First();
AutomationElement browserInstance = System.Windows.Automation.AutomationElement.FromHandle(process.MainWindowHandle);
TreeWalker tw = new TreeWalker(new PropertyCondition(AutomationElement.ClassNameProperty, "SearchBar"));
AutomationElement searchBar = tw.GetFirstChild(browserInstance);
myElement.SetFocus();
Thread.Sleep(1000);
searchBar.SetFocus();
Thread.Sleep(1000);
SendKeys.SendWait("Hello, world!");
}
이 부분에서 몇가지 질문이 생기는 분이 계실지 모르겠습니다. 예를 들면 "왜 내가 IValueProvider를 구현했을까?". 위의 코드 조각은 사용자 입력을 시뮬레이션 합니다. 먄약 저것이 우리 것이 아니라면 ValuePattern 으로 부터 와야합니다. 개인적으로, 저는 ValuePattern/TryGetCurrentPattern/etc 의 상호작용을 알아냈고 꽤 거추장스러운 전체 경험을 발견했습니다. 밑에 코드에서 제가 뜻하는 바를 알 수 있을 겁니다.
[TestMethod]
public void TestMethod1()
{
Process process = System.Diagnostics.Process.GetProcessesByName("iexplore").First();
AutomationElement myElement = System.Windows.Automation.AutomationElement.FromHandle(process.MainWindowHandle);
TreeWalker tw = new TreeWalker(new PropertyCondition(AutomationElement.ClassNameProperty, "SearchBar"));
AutomationElement searchBar = tw.GetFirstChild(myElement);
object valuePattern;
searchBar.TryGetCurrentPattern(ValuePattern.Pattern, out valuePattern);
((ValuePattern)valuePattern).SetValue("Hello, world!");
}
이글이 결코 이해하기 좋은 가이드라인은 아니지만 UI 자동화가 어떻게 진행되는지 관심이 있는 몇몇 분들에게는 충분할 것이라는 생각이 듭니다.
----------------------------------------------------------------------------------------------------------
흠냐... 번역이 좀 엉터리인 부분이 있고.. 글도 생각보다 어려워서 좀 걱정이 되는군요. 또 마지막 부분은 저도 이해하지 못한 부분이고 구현도 되지 않아서 여러분의 도움을 요청합니다.^^;;
일단 따라해본 샘플 프로젝트를 첨부합니다.
Part2 가 있어서 조금더 쉽게 구현하는 방법이 소개 되어있으니 그 부분만 따라하셔도 아마 자동화 부분은 해결하실 수 있으실 것이라 봅니다. 그럼..^^
- smile -