컨트롤의 코딩된 UI 테스트 사용
코딩된 UI 테스트 프레임워크에 대한 지원을 구현하면 컨트롤을 보다 쉽게 테스트할 수 있습니다.지원 수준 증가는 점진적으로 추가할 수 있습니다.이를 위해서는 먼저 기록 및 재생 및 속성 유효성 검사 지원부터 시작합니다.이를 기반으로 사용해서 코딩된 UI 테스트 빌더가 사용자 컨트롤의 사용자 정의 속성을 인식하도록 허용하고 생성된 코드에서 이러한 속성에 액세스하기 위한 사용자 정의 클래스를 제공할 수 있습니다.또한 기록 중인 작업의 의도와 가까운 방식으로 코딩된 UI 테스트 빌더가 작업을 캡처하도록 할 수 있습니다.
항목 내용
내게 필요한 옵션을 구현하여 기록 및 재생, 속성 유효성 검사를 지원
코딩된 UI 테스트 빌더는 기록 중 발견된 컨트롤에 대한 정보를 캡처하고 해당 세션을 재생하기 위한 코드를 생성합니다.컨트롤이 액세스 가능성을 지원하지 않을 경우, 코딩된 UI 테스트 빌더는 화면 좌표를 사용하여 작업(예: 마우스 클릭)을 캡처합니다.테스트를 재생하면 생성된 코드가 동일 화면 좌표에서 이러한 마우스 클릭을 실행합니다.테스트를 재생할 때 컨트롤이 화면의 다른 위치에 표시되면 생성된 코드가 컨트롤에 대해 해당 작업을 수행하는 데 실패합니다.다른 화면 구성 또는 다른 환경에서 테스트를 재생하거나 UI 레이아웃이 변경된 다음에 재생할 경우에는 작업이 실패할 수 있습니다.
그래도 액세스 가능성을 구현할 경우, 코딩된 UI 테스트 빌더는 테스트를 기록하고 코드를 생성할 때 이를 사용해서 컨트롤에 대한 정보를 캡처합니다.그런 다음 테스트를 실행하면 컨트롤이 사용자 인터페이스에서 다른 위치에 있더라도 생성된 코드가 컨트롤에 대해 해당 이벤트를 재생합니다.테스트 작성자는 또한 컨트롤의 기본 속성을 사용해서 어설션을 만들 수 있습니다.
Windows Forms 컨트롤의 기록 및 재생, 속성 유효성 검사 및 탐색을 지원하려면
다음 절차의 개요 및 AccessibleObject의 자세한 설명에 따라 컨트롤에 대해 액세스 가능성을 구현합니다.
ControlAccessibleObject에서 파생되는 클래스를 구현하고 클래스의 개체를 반환하도록 AccessibilityObject 속성을 재정의합니다.
public partial class ChartControl : UserControl { // Overridden to return the custom AccessibleObject for the control. protected override AccessibleObject CreateAccessibilityInstance() { return new ChartControlAccessibleObject(this); } // Inner class ChartControlAccessibleObject represents accessible information // associated with the ChartControl and is used when recording tests. public class ChartControlAccessibleObject : ControlAccessibleObject { ChartControl myControl; public ChartControlAccessibleObject(ChartControl ctrl) : base(ctrl) { myControl = ctrl; } } }
액세스 가능한 개체의 Role, State, GetChild 및 GetChildCount 속성과 메서드를 재정의합니다.
자식 컨트롤에 대해 또 다른 액세스 가능성 개체를 구현하고 이 액세스 가능성 개체를 반환하도록 자식 컨트롤의 AccessibilityObject 속성을 재정의합니다.
자식 컨트롤의 액세스 가능성 개체에 대해 Bounds, Name, Parent, Role, State, Navigate 및 Select 속성과 메서드를 재정의합니다.
[!참고]
이 항목에서는 이 절차의 AccessibleObject에 있는 액세스 가능성 샘플로부터 시작해서 남은 절차의 설명에 따라 빌드합니다.액세스 가능성 샘플의 작동 가능 버전을 만들려면 콘솔 응용 프로그램을 만들고 Program.cs의 코드를 샘플 코드로 바꿉니다.Accessibility, System.Drawing 및 System.Windows.Forms에 대한 참조를 추가해야 합니다.빌드 경고를 제거하려면 Accessibility에 대한 Interop 형식 포함을 False로 변경해야 합니다.프로젝트의 출력 형식을 콘솔 응용 프로그램에서 Windows 응용 프로그램을 변경하면 응용 프로그램을 실행할 때 콘솔 창이 표시되지 않습니다.
속성 공급자를 구현하여 사용자 지정 속성의 유효성 검사를 지원
기록 및 재생과 속성 유효성 검사를 위한 기본 지원을 구현한 다음에는 UITestPropertyProvider 플러그인을 구현해서 코딩된 UI 테스트에서 컨트롤의 사용자 지정 속성을 사용할 수 있도록 지정할 수 있습니다.예를 들어 다음 절차에서는 코딩된 UI 테스트가 차트 컨트롤의 CurveLegend 자식 컨트롤의 State 속성에 액세스할 수 있도록 속성 공급자를 만듭니다.
사용자 지정 속성의 유효성 검사를 지원하려면
액세스 가능한 개체의 Description 속성을 재정의해서 기본 설명(다중 속성을 구현하는 경우에는 여러 설명)과는 세미콜론(;)으로 구분된 설명 문자열에 다양한 속성 값을 전달합니다.
public class CurveLegendAccessibleObject : AccessibleObject { // add the state property value to the description public override string Description { get { // Add “;” and the state value to the end // of the curve legend’s description return "CurveLegend; " + State.ToString(); } } }
클래스 라이브러리 프로젝트를 만들어서 컨트롤에 대한 UI 테스트 확장 패키지를 만들고 Accessibility, Microsoft.VisualStudio.TestTools.UITesting, Microsoft.VisualStudio.TestTools.UITest.Common 및 Microsoft.VisualStudio.TestTools.Extension에 대한 참조를 추가합니다.Accessibility에 대한 Interop 형식 포함을 False로 변경합니다.
UITestPropertyProvider에서 파생되는 속성 공급자 클래스를 추가합니다.
using System; using System.Collections.Generic; using Accessibility; using Microsoft.VisualStudio.TestTools.UITesting; using Microsoft.VisualStudio.TestTools.UITest.Extension; using Microsoft.VisualStudio.TestTools.UITesting.WinControls; using Microsoft.VisualStudio.TestTools.UITest.Common; namespace ChartControlExtensionPackage { public class ChartControlPropertyProvider : UITestPropertyProvider { } }
Dictionary에 속성 이름 및 속성 설명을 배치하여 속성 공급자를 구현합니다.
// Define a map of property descriptors for CurveLegend private static Dictionary<string, UITestPropertyDescriptor> curveLegendPropertiesMap = null; private static Dictionary<string, UITestPropertyDescriptor> CurveLegendPropertiesMap { get { if (curveLegendPropertiesMap == null) { UITestPropertyAttributes read = UITestPropertyAttributes.Readable | UITestPropertyAttributes.DoNotGenerateProperties; curveLegendPropertiesMap = new Dictionary<string, UITestPropertyDescriptor> (StringComparer.OrdinalIgnoreCase); curveLegendPropertiesMap.Add("State", new UITestPropertyDescriptor(typeof(string), read)); } return curveLegendPropertiesMap; } } // return the property descriptor public override UITestPropertyDescriptor GetPropertyDescriptor(UITestControl uiTestControl, string propertyName) { return CurveLegendPropertiesMap[propertyName]; } // return the property names public override ICollection<string> GetPropertyNames(UITestControl uiTestControl) { if (uiTestControl.ControlType.NameEquals("Chart") || uiTestControl.ControlType.NameEquals("Text")) { // the keys of the property map are the collection of property names return CurveLegendPropertiesMap.Keys; } // this is not my control throw new NotSupportedException(); } // Get the property value by parsing the accessible description public override object GetPropertyValue(UITestControl uiTestControl, string propertyName) { if (String.Equals(propertyName, "State", StringComparison.OrdinalIgnoreCase)) { object[] native = uiTestControl.NativeElement as object[]; IAccessible acc = native[0] as IAccessible; string[] descriptionTokens = acc.accDescription.Split(new char[] { ';' }); return descriptionTokens[1]; } // this is not my control throw new NotSupportedException(); }
어셈블리가 컨트롤 및 해당 자식 항목에 대한 컨트롤 특정 지원을 제공함을 나타내도록 UITestPropertyProvider.GetControlSupportLevel을 재정의합니다.
public override int GetControlSupportLevel(UITestControl uiTestControl) { // For MSAA, check the control type if (string.Equals(uiTestControl.TechnologyName, "MSAA", StringComparison.OrdinalIgnoreCase) && (uiTestControl.ControlType == "Chart"||uiTestControl.ControlType == "Text")) { return (int)ControlSupport.ControlSpecificSupport; } // This is not my control, so return NoSupport return (int)ControlSupport.NoSupport; }
UITestPropertyProvider의 남아 있는 추상 메서드를 재정의합니다.
public override string[] GetPredefinedSearchProperties(Type specializedClass) { throw new NotImplementedException(); } public override Type GetSpecializedClass(UITestControl uiTestControl) { throw new NotImplementedException(); } public override Type GetPropertyNamesClassType(UITestControl uiTestControl) { throw new NotImplementedException(); } public override void SetPropertyValue(UITestControl uiTestControl, string propertyName, object value) { throw new NotImplementedException(); } public override string GetPropertyForAction(UITestControl uiTestControl, UITestAction action) { throw new NotImplementedException(); } public override string[] GetPropertyForControlState(UITestControl uiTestControl, ControlStates uiState, out bool[] stateValues) { throw new NotImplementedException(); }
UITestExtensionPackage에서 파생된 확장 패키지 클래스를 추가합니다.
using System; using Microsoft.VisualStudio.TestTools.UITesting; using Microsoft.VisualStudio.TestTools.UITest.Extension; using Microsoft.VisualStudio.TestTools.UITest.Common; namespace ChartControlExtensionPackage { internal class ChartControlExtensionPackage : UITestExtensionPackage { } }
어셈블리에 대한 UITestExtensionPackage 특성을 정의합니다.
[assembly: Microsoft.VisualStudio.TestTools.UITest.Extension.UITestExtensionPackage( "ChartControlExtensionPackage", typeof(ChartControlExtensionPackage.ChartControlExtensionPackage))] namespace ChartControlExtensionPackage { …
확장 패키지 클래스에서 속성 공급자가 요청될 때 속성 공급자 클래스를 반환하도록 UITestExtensionPackage.GetService를 재정의합니다.
internal class ChartControlExtensionPackage : UITestExtensionPackage { public override object GetService(Type serviceType) { if (serviceType == typeof(UITestPropertyProvider)) { if (propertyProvider == null) { propertyProvider = new ChartControlPropertyProvider(); } return propertyProvider; } return null; } private UITestPropertyProvider propertyProvider = null; }
UITestExtensionPackage의 남은 추상 메서드 및 속성을 재정의합니다.
public override void Dispose() { } public override string PackageDescription { get { return "Supports coded UI testing of ChartControl"; } } public override string PackageName { get { return "ChartControl Test Extension"; } } public override string PackageVendor { get { return "Microsoft (sample)"; } } public override Version PackageVersion { get { return new Version(1, 0); } } public override Version VSVersion { get { return new Version(10, 0); } }
이진 파일을 빌드하여 %ProgramFiles%\Common Files\Microsoft Shared\VSTT\10.0\UITestExtensionPackages에 복사합니다.
[!참고]
이 확장 패키지는 "Text" 형식의 모든 컨트롤에 적용됩니다.같은 형식의 여러 컨트롤을 테스트할 경우, 개별적으로 테스트하고 테스트를 기록할 때 배포되는 확장 패키지를 관리해야 합니다.
사용자 지정 속성에 액세스하기 위해 클래스를 구현하여 코드 생성을 지원
코딩된 UI 테스트 빌더는 세션 기록에서 코드를 생성할 때 UITestControl 클래스를 사용해서 컨트롤에 액세스합니다.
UITestControl uIAText = this.UIItemWindow.UIChartControlWindow.UIAText;
Assert.AreEqual(this.AssertMethod3ExpectedValues.UIATextState, uIAText.GetProperty("State").ToString());
컨트롤의 사용자 정의 속성에 대한 액세스를 제공하기 위해 속성 공급자를 구현한 경우 생성된 코드가 간소화되도록 해당 속성에 액세스하는 데 사용되는 특수한 클래스를 추가할 수 있습니다.
ControlLegend uIAText = this.UIItemWindow.UIChartControlWindow.UIAText;
Assert.AreEqual(this.AssertMethod3ExpectedValues.UIATextState, uIAText.State);
컨트롤에 액세스하는 특수화 클래스를 추가하려면
WinControl에서 파생된 클래스를 구현하고 생성자의 검색 속성 컬렉션에 이 컨트롤의 형식을 추가합니다.
public class CurveLegend:WinControl { public CurveLegend(UITestControl c) : base(c) { // The curve legend control is a “text” type of control SearchProperties.Add( UITestControl.PropertyNames.ControlType, "Text"); } }
컨트롤의 사용자 지정 속성을 클래스의 속성으로 구현합니다.
public virtual string State { get { return (string)GetProperty("State"); } }
Curve Legend 자식 컨트롤에 대한 새 클래스의 형식을 반환하도록 속성 공급자의 UITestPropertyProvider.GetSpecializedClass 메서드를 재정의합니다.
public override Type GetSpecializedClass(UITestControl uiTestControl) { if (uiTestControl.ControlType.NameEquals("Text")) { // This is text type of control. For my control, // that means it’s a curve legend control. return typeof(CurveLegend); } // this is not a curve legend control return null; }
새로운 클래스의 PropertyNames 메서드의 형식을 반환하도록 속성 공급자의 GetPropertyNamesClassType 메서드를 재정의합니다.
public override Type GetPropertyNamesClassType(UITestControl uiTestControl) { if (uiTestControl.ControlType.NameEquals("Text")) { // This is text type of control. For my control, // that means it’s a curve legend control. return typeof(CurveLegend.PropertyNames); } // this is not a curve legend control return null; }
작업 필터를 구현하여 의도 인식 작업을 지원
Visual Studio는 테스트를 기록할 때 각 마우스 및 키보드 이벤트를 캡처합니다.하지만 일부 경우에는 너무 많은 마우스 및 키보드 이벤트가 포함됨에 따라 작업의 목적이 손실될 수 있습니다.예를 들어 컨트롤에 자동 완성이 지원될 경우 동일한 마우스 및 키보드 이벤트 집합으로도 다른 환경에서는 테스트를 재생할 때 다른 값이 발생할 수 있습니다.일련의 키보드 및 마우스 이벤트를 단일 작업으로 대체하는 작업 필터 플러그인을 추가할 수 있습니다.이 방식을 사용하면 특정 값을 선택하는 일련의 마우스 및 키보드 이벤트를 해당 값을 설정하는 단일 작업으로 대체할 수 있습니다.이렇게 하면 각 환경 간에 달라지는 자동 완성 기능으로부터 코딩된 UI 테스트를 보호할 수 있습니다.
의도 인식 작업을 지원하려면
ApplyTimeout, Category, Enabled, FilterType, Group 및 Name 속성을 재정의하여 UITestActionFilter에서 파생된 작업 필터 클래스를 구현합니다.
internal class MyActionFilter : UITestActionFilter { // If the user actions we are aggregating exceeds the time allowed, // this filter is not applied. (The timeout is configured when the // test is run.) public override bool ApplyTimeout { get { return true; } } // Gets the category of this filter. Categories of filters // are applied in priority order. public override UITestActionFilterCategory Category { get { return UITestActionFilterCategory.PostSimpleToCompoundActionConversion; } } public override bool Enabled { get { return true; } } public override UITestActionFilterType FilterType { // This action filter operates on a single action get { return UITestActionFilterType.Unary; } } // Gets the name of the group to which this filter belongs. // A group can be enabled/disabled using configuration file. public override string Group { get { return "ChartControlActionFilters"; } } // Gets the name of this filter. public override string Name { get { return "Convert Double-Click to Single-Click"; } }
ProcessRule을 재정의합니다.이 예제에서는 두 번 클릭 작업을 단일 클릭 작업으로 바꿉니다.
public override bool ProcessRule(IUITestActionStack actionStack) { if (actionStack.Count > 0) { MouseAction lastAction = actionStack.Peek() as MouseAction; if (lastAction != null) { if (lastAction.UIElement.ControlTypeName.Equals( ControlType.Text.ToString(), StringComparison.OrdinalIgnoreCase)) { if(lastAction.ActionType == MouseActionType.DoubleClick) { // Convert to single click lastAction.ActionType = MouseActionType.Click; } } } } // Do not stop aggregation return false; }
확장 패키지의 GetService 메서드에 작업 필터를 추가합니다.
public override object GetService(Type serviceType) { if (serviceType == typeof(UITestPropertyProvider)) { if (propertyProvider == null) { propertyProvider = new PropertyProvider(); } return propertyProvider; } else if (serviceType == typeof(UITestActionFilter)) { if (actionFilter == null) { actionFilter = new RadGridViewActionFilter(); } return actionFilter; } return null; }
바이너리를 빌드하고 이를 %ProgramFiles%\Common Files\Microsoft Shared\VSTT\10.0\UITestExtensionPackages에 복사합니다.
[!참고]
작업 필터는 액세스 가능성 구현 또는 속성 공급자에 종속되지 않습니다.
속성 공급자 또는 작업 필터 디버그
속성 공급자 및 작업 필터는 응용 프로그램과 별도의 프로세스에서 코딩된 UI 테스트 빌더에 의해 로드 및 실행되는 확장 패키지에 구현됩니다.
속성 공급자 또는 작업 필터를 디버그하려면
확장 패키지의 디버그 버전을 빌드하고, .dll 및 .pdb 파일을 %ProgramFiles%\Common Files\Microsoft Shared\VSTT\10.0\UITestExtensionPackages에 복사합니다.
(디버거가 아닌 위치에서) 응용 프로그램을 실행합니다.
코딩된 UI 테스트 빌더를 실행합니다.
codedUITestBuilder.exe /standalone
디버거를 codedUITestBuilder 프로세스에 연결합니다.
코드에 중단점을 설정합니다.
코딩된 UI 테스트 빌더에서, 속성 공급자를 실행하기 위한 어설션을 만들고, 작업 필터를 실행하기 위한 작업을 기록합니다.
외부 리소스
지침
Visual Studio 2012를 사용한 연속 배달 테스트 - 2장: 단위 테스트: 내부 테스트