May 22 2009

Major Tom 的 GUI 控件

Category: Testing Ryan @ 12:46
Major Tom 的 GUI 控件
John Robbins


代码下载位置: Bugslayer2007_03.exe (199 KB)
Browse the Code Online

在我的 14 年的 Windows 开发生涯中,我曾经见过的绝对最佳演示是在 2003 年 Longhorn 召开的专业开发人员大会 (PDC)。有关 Windows® Presentation Foundation 和 Visual Studio® 2005 试用版的讨论就已经让很多与会者感到兴奋,而 Active Accessibility® 程序经理更是赢得了我在技术大会上见过的唯一一次全场起立鼓掌。他演示了一个允许针对 GUI 应用程序的单独输入队列的“Longhorn”版本(现在叫做 Windows Vista™)。他的演示是在前台玩纸牌游戏的同时,在后台使用 Active Accessibility 中基本自动化 API 的基于 Microsoft® .NET 的应用程序,从设备管理器添加和删除硬件设备!他甚至在设备管理器中四处单击,但所有输入都被忽略,这是因为控制器程序对该输入队列有完全控制权。
Windows 开发中的问题在于:当您试图通过鼠标和键盘播放实用程序自动执行 GUI 应用程序时(例如,我在以前的 Bugslayer 专栏(英文)中开发的 Tester 应用程序),只是在错误时间碰一下鼠标就会打断自动化。人们之所以对这个 PDC 演示感到如此兴奋,是因为它实现了这样一个梦想:既能自动执行应用程序 GUI 部分,又能保证播放内容完全是所希望的结果。如果您要编写任何种类的基于 Windows 的应用程序,那么您已获得它的具有 GUI 的某个部分,它可以是整个应用程序、安装或符合 AJAX 的最新手段。能够一致和正确地测试应用程序的 GUI 部分是编写出优秀代码的关键。
在我编写此专栏的时候,Windows Vista 已发布并准备投入生产。遗憾的是,赢得如此多的赞誉的多输入队列功能却没有进入最后的版本中。但是,GUI 自动化的出色部分确实已融入 Active Accessibility 中。另外,可通过 .NET Framework 3.0 获得新的 GUI 自动化部分,因此它不仅可以在 Windows Vista 上工作,而且可以在 Windows XP 和 Windows Server® 2003 上工作。此外,Active Accessibility 代码还能够与 Windows Presentation Foundation UI 和基于 HWND 的应用程序无缝配合。也许下一次发布的操作系统将实现单独的输入队列的最终目标,但由于具有新的 Microsoft UI 自动化,使应用程序的 GUI 部分实现自动化变得更为容易,而且目前就可以使用它。
在这个专栏中,我的目标是介绍如何使用 UI 自动化工具和 API 实现初步的 GUI 自动化。与大多数 Bugslayer 专栏一样,我将提供一大组代码,使您的 GUI 测试比直接使用 API 更轻松。我假设您已读完 msdn2.microsoft.com/ms747327.aspx中的文档(英文),如果还没有,应当现在就阅读它们,否则本专栏的其余内容对您没有什么意义。提醒您一下,您需要安装 .NET Framework 3.0 以及 Windows Vista 和 .NET Framework 3.0 软件开发工具包


 

Microsoft UI 自动化
在阅读 Microsoft UI 自动化文档时,您将发现在 Active Accessibility 中真的有生活的根源。很多讨论都围绕查找窗口中所有特定的子控件。由于屏幕读取程序和其他帮助工具需要快速获得该信息,所以,通过 API 轻松为基于 HWND 的应用程序和 Windows Presentation Foundation 应用程序获得这些树,而不必由您自己编写两组单独的代码,是个好办法。
如何编写自己的提供程序,使其以统一的方式通过 Microsoft UI 自动化 API 服务于自定义控件,关于这一点还有非常精彩的讨论。如果您要编写自己的唯一自定义控件,您一定想阅读“UI 自动化提供程序概述”(msdn2.microsoft.com/ms750446.aspx),以便任何使用帮助技术的人都可以使用您的应用程序。创建提供程序的工作并不太麻烦,但它不在本专栏要讨论的自动测试的范围内。
Microsoft UI 自动化 API 的非常有趣的一部分是它的出色的事件支持。虽然可以安装日记挂钩,用于记录计算机的键盘和鼠标使用情况,但却没有清晰的 API 来获得在窗口打开和关闭时及其他情况下哪个控件获得焦点的通知。如果回头看一下我的 Tester 应用程序的代码,就可以看到我使用基于计算机培训 (CBT) 挂钩和大量猜测来为基于 HWND 的应用程序获得这些通知。我发现的有关新事件系统的最好消息是:可以隔离事件通知,从而可以获得从单个控件、到有子控件的窗口、乃至整个桌面的所有通知。若要查看使用事件控件来记录应用程序的使用情况的示例,测试脚本生成器示例可以提供很大帮助 (msdn2.microsoft.com/ms771275.aspx)。
整个 Microsoft UI 自动化 API 的关键点是所有一切都基于控件类型。AutomationElement 类的实例包装每个控件,这些控件允许您快速识别出屏幕上是哪个特定控件以及它的作用。过去,需要通过商业应用程序来提供这种级别的详细信息,而且如果您以前不得不利用这种昂贵的自定义自动测试工具,那么您会发现通过免费下载就可以获得所有东西是非常好的事情。
已经有 38 个定义好的控件类型,它们涵盖了从按钮到数据网格的所有控件 (msdn2.microsoft.com/ms749005.aspx)。从自动化测试观点看,使提供程序模型有趣的是这些控件类型准确显示了它们分别支持哪些属性和控件模式。您可以猜测到,这些属性是对该控件类型必需或可选的基础 AutomationElement 类的属性的子集。例如,按钮控件应当支持 AcceleratorKeyProperty,以返回用于该按钮的加速键。
您将注意到控件模式更为有趣一点。UI 自动化控件模式概述文档 (msdn2.microsoft.com/ms752362.aspx)(英文)对它进行了最佳概括:“有了控件模式,就可以在独立于控件类型或控件外观的情况下分类和公开控件的功能。”稍后我将在专栏中介绍,我们将在自动执行 GUI 时更多地用到控件模式。控件模式如此受欢迎的原因是,一旦获得控件的特定模式,该模式将完成执行该操作的工作。例如,如果您正在处理按钮,则 IInvokePattern 接口会公开一个可执行单击按钮操作的 Invoke 方法。这样,就不必纠缠于 Windows 消息或 SendInput API。
虽然在文档中除了示例和一点阅读内容之外,关于使用 API 进行自动化测试没有详细介绍,但要看出需要什么并不太难。在这里必须要感谢设计 UI 自动化 API 的团队在国际化方面做出的努力。在使用以前的工具和 API 实现自动化时,您被迫要嵌入类似“文件”和“打开”这样的字符串,才能找到菜单项目。这意味着,您必须在 GUI 自动化脚本内部提供所有国际化字符串,而这很麻烦,并且会使测试脚本的可重用性降低。今天,如果您要访问应用程序中的特定控件,可以使用 AutomationId 属性。不管是什么区域设置,此名称都是唯一的,并且保证它在任何计算机上都是相同的。现在,您可以重用这些脚本,并且极大地帮助您的翻译人员,因为他们可以像您那样测试应用程序。
从 UI 自动化 API 丢失的一个 UI 测试功能是鼠标支持。它根本就没有此功能。对于大多数应用程序来说,这不是大问题,因为控件模式提供了等效功能。但是,如果您正在处理画图应用程序,则在用鼠标绘制这些笑脸时不会找到任何帮助。在这种情况下,仍然可以使用像 Tester(可提供鼠标支持)这样的工具,它通过与使用 UI 自动化 API 的程序配合,来实现需要依赖鼠标获得的效果。


 

UISpy
在介绍我为了方便 UI 测试而编写的酷代码之前,我需要先说一说您要广泛使用以帮助编写测试过程的工具:UISpy(如图 1 所示)。可以在 Windows Vista 和 .NET Framework 3.0 软件开发工具包中找到 UISpy。实质上,UISpy 工具可以运行并以 Microsoft 自动化 UI 格式显示桌面所有控件,从而使您可以开始看见哪些属性是用于您的特定控件的。另外,在使用 Microsoft 自动化 UI API 时,可以执行控件上的模式和查询特定属性。
图 1 UISpy 用户界面 (单击该图像获得较大视图)
只需使用该工具并逐一单击 UISpy 菜单,您就会明白它的工作原理。但是,我已在 UISpy 上花了大量时间来使本月专栏中附带的代码正确工作,而且为了减少您的麻烦,我要介绍我遇到的几个障碍。
UISpy 中的树控件用于显示特定视图的层次结构:原始、控件或内容。原始视图显示与特定本机编程结构最紧密相关的层次结构,它是您很少使用的视图。控件视图则显示当映射到用户看到用户界面时所感受到的内容的层次结构。内容视图(它是控件视图的子集)则只显示在用户界面中有真实信息的控件。使用 Microsoft 自动化 UI API 时,您将使用这些层次结构中的某一个来访问您的应用程序中的控件。
在 UISpy 中,在控件视图树控件中似乎有一个 Bug:当右键单击节点时,UISpy 不能始终更新所选内容。由于右键单击菜单对于刷新视图、设置焦点或访问控件模式非常重要,您将发现是在前一个所选节点上执行菜单选项。一旦要访问右键菜单,请始终先用鼠标选择该项,然后再右键单击它。不知道有多少次我查看的是错误控件的控件模式!
在 Microsoft 自动化 UI API 的文档中,有几处提到使用根元素(桌面窗口)作为搜索的起点非常缓慢。请相信文档的这个说法。无论何时使用 UISpy 查看您的应用程序,请立即选择并右键单击应用程序的子节点,并选择“Scope to Element”。这将使所有焦点跟踪和突出显示仅限于您感兴趣使用的应用程序。
UISpy 的一个出色功能是悬停模式,它允许您将鼠标移到屏幕上的控件上面,然后,当您按 Ctrl 键时,就会跳到所有打开的树控件中的该节点上。悬停模式在您要查看菜单项时尤其重要,因为只有在菜单展开后您才会看到子节点。问题是 UISpy 并不能始终意识到 Ctrl 键已按下,因此请养成多次按 Ctrl 键的习惯。
UISpy 的最后一个问题也是最坏的问题不是 UISpy 的错误,但当您试图确定如何自动执行您不拥有的用户界面组成部分时,它就会让您着急上火。一个最好的例子是 Windows Vista 上新的公用打开对话框。看一下这个对话框,大多数控件都像典型的基于 HWND 的编辑框、按钮、列表控件等等。但不要让眼睛欺骗您。像“打开”按钮的东西在 UISpy 中有时被报告成按钮控件,有时却报告成窗格控件,按照文档的解释,这“表示分组级别小于窗口或文档,但高于单个控件”。
如果您对您尝试自动执行的用户界面没有所有权,就需要用 UISpy 多次检查它,以准确确定它到底是什么控件类型。在使用悬停模式和全焦点跟踪模式(在 UISpy 树视图中,后者会跳到有焦点的控件)之间,您最后将会发现这些神秘控件的确切类型。


 

更好的 UI 自动化测试
一旦我了解了如何使用 UISpy,并对基本 API 有了大致了解,我就开始使用 Microsoft UI 自动化 API 来驱动每个人都喜欢的应用程序:记事本。我打算先了解 API,以便知道如何在 Visual Studio Team System 中将自动化测试集成到精彩的单元测试工具中。您可以使用 NUnit (nunit.org) 来完成本质上相同的事情。
通过使用直接的 Microsoft UI 自动化 API,我编写了执行以下操作的应用程序:
  • 确保进程可感知 DPI
  • 启动记事本,并执行“文件”|“打开”菜单
  • 在“打开”对话框中,打开程序的源代码
  • 执行“帮助”|“关于”菜单,并单击“确定”按钮
  • 执行“文件”|“退出”菜单
图 2 中可以看到此程序的一部分,我强烈建议您阅读完所有代码和注释,因为它将使您很好地了解如何使用 Microsoft UI 自动化 API。请注意,代码中有没有错误处理,这是为了尽量侧重于核心 API 用法。
#region Using Statements
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Threading;
using System.ComponentModel;
using System.IO;
using System.Globalization;
using System.Windows.Forms;
using System.Runtime.InteropServices;
#endregion

namespace RawUsageExample
{
    internal static class NativeMethods
    {
        // The Windows Vista DPI aware function.
        [DllImport ( "user32.dll" , EntryPoint = "SetProcessDPIAware" )]
        [return: MarshalAs ( UnmanagedType.Bool )]
        private static extern bool RealSetProcessDPIAware ( );

        internal static bool SetProcessDPIAware ( )
        {
            Boolean returnValue = false;
            try
            {
                returnValue = RealSetProcessDPIAware ( );
            }
            catch ( EntryPointNotFoundException )
            {
                // Not running on Windows Vista.
            }
            return ( returnValue );
        }
    }

    class Program
    {
        [STAThread]
        static void Main ( )
        {
            // Set this process to DPI aware if running on Windows Vista.
            NativeMethods.SetProcessDPIAware ( );

            // Start Notepad and connect up to it.            
            // Wait for the process to get going enough to have a UI.
            Process proc = Process.Start ( "notepad" );
            Thread.Sleep ( 1000 );

            // Attach to the main window.
            AutomationElement rootElement = 
                AutomationElement.FromHandle ( proc.MainWindowHandle );

            // Create the AndCondition to find the menuBar.
            AndCondition menuBarFind = new AndCondition (
                new PropertyCondition (
                    AutomationElement.ControlTypeProperty, 
                    ControlType.MenuBar ) ,
                new PropertyCondition ( 
                    AutomationElement.AutomationIdProperty, "MenuBar" ) ,
                Automation.ControlViewCondition );

            // Find the menuBar, which is a child of the main window.
            AutomationElement menuBarElement =
                rootElement.FindFirst ( TreeScope.Children, menuBarFind );

            // Create the AndCondition to find the File menu.
            AndCondition fileMenuFind = new AndCondition (
                 new PropertyCondition ( 
                     AutomationElement.ControlTypeProperty ,
                     ControlType.MenuItem ) ,
                 new PropertyCondition (     
                     AutomationElement.AutomationIdProperty , "Item 1" ) ,
                 Automation.ControlViewCondition );

            // Find the File menu.
            AutomationElement fileMenuElement =
                menuBarElement.FindFirst ( TreeScope.Children , 
                fileMenuFind );

            // Get the control pattern for ExpandCollapse and do the 
            // Expand to get the children.
            ExpandCollapsePattern fileExpandPattern = fileMenuElement.
                GetCurrentPattern ( ExpandCollapsePattern.Pattern )
                    as ExpandCollapsePattern;
            fileExpandPattern.Expand ( );
            Thread.Sleep ( 1000 );

            // Create the AndCondition to find the Open menu.
            AndCondition openMenuFind = new AndCondition (
                 new PropertyCondition ( 
                     AutomationElement.ControlTypeProperty ,
                     ControlType.MenuItem ) ,
                 new PropertyCondition (
                     AutomationElement.AutomationIdProperty , "Item 2" ) ,
                     Automation.ControlViewCondition );

            // Find the Open menu.
            AutomationElement openMenuElement =
                fileMenuElement.FindFirst ( TreeScope.Descendants,
                    openMenuFind );

            // Get the InvokePattern off the openMenu.
            InvokePattern openInvokePattern =
                openMenuElement.GetCurrentPattern ( 
                    InvokePattern.Pattern ) as InvokePattern;

            // Invoke the menu to get the Open dialog.
            openInvokePattern.Invoke ( );
            Thread.Sleep ( 1000 );

            ...

通读图 2 中的代码后,您可能会眼前一亮,因为在 Microsoft UI 自动化 API 使您拥有强大能力的同时,它却不得不成为曾经开发的最复杂的 API 之一。图 2 中的大多数代码只是打开“文件”|“打开”菜单。直接使用 Microsoft UI 自动化 API 意味着您必须反复执行略微不同的 5 到 10 行代码(并且多次反复!)。实际上,完整代码要用 250 行来执行我前面讨论的五个项目符号点中的步骤。像这样的 API 问题会使开发人员求助于“编辑器继承”(也称为剪切和粘贴)来建立他们的单元测试,从而导致代码中出现所有种类的 Bug。而这将导致 API 被中止使用。
虽然我可以看出 API 设计人员试图完成什么目标,但主要的错误在于它是个复杂的 API;对于每个单独的控件类型来说,您正在处理的所有东西只是一个类:AutomationElement。这会导致七行代码查找“文件”菜单、查找 ExpandCollapsePattern、执行实际展开并使 GUI 有时间显示菜单。有时,当您试图获得无限灵活性时,却会得到比实际使用水平更难使用的 API。
正是在我第二次试图了解 Microsoft UI 自动化 API 之后,我不得不设法将所有 38 个控件类型封装在其各自的类中,以便您能够确定所使用的控件的确切类型。此外,我希望将尽可能多的连续单调的工作隐藏在原始 API 中,但仍然为您需要执行的任何提前工作提供对基本 API 的访问权限。图 3 中的代码显示了与图 2 中相同的程序,但使用的是 Bugslayer.TestTools.GuiAutomation 命名空间代码。再次说明,我没有将任何错误检查的重点始终放在代码上。
#region Using Statements
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Threading;
using System.ComponentModel;
using System.IO;
using System.Globalization;
using System.Windows.Forms;
using Bugslayer.TestTools.GuiAutomation;
#endregion

namespace UIUsageExample
{
    class Program
    {
        [STAThread]
        static void Main ( )
        {
            // Start Notepad and connect up to it.
            // Wait for the process to get going enough to have a UI.
            Process proc = Process.Start ( "notepad" );
            Thread.Sleep ( 1000 );
            UIWindow notepadWindow = new UIWindow ( proc );

            // Now we're on the main window so execute the 
            // File Open command.
            UIMenuBar menuBar = notepadWindow.FindChild ( 
                "MenuBar" , ControlType.MenuBar ) as UIMenuBar ;

            // This wraps all the goo necessary to invoke the Open menu.
            menuBar.InvokeMenus ( "Item 1" , "Item 2" );

            ...


您可能注意到,图 3 中的代码比图 2 中的代码短很多。实际上,完整程序的长度只有 81 行,这只是原始代码的 32%!我还愿意打赌:您将发现使用 Bugslayer.TestTools.GuiAutomation 的代码更容易阅读和理解。当然,如果代码易于使用,您就更有可能使用它。


 

UIWindow 类
在这里,您可能希望下载本月的 Bugslayer 的代码,以便好好看一下。您可以同时进行,因为我希望向您介绍一些实现细节。如果您有 Visual Studio Team Developer Edition 或 Visual Studio Team Suite,请打开 Bugslayer.TestTools.GuiAutomation\Bugslayer.TestTools.GuiAutomation.SLN 文件,看到这两处代码和单元测试。如果只有 Visual Studio 专业版,则需要打开 UIUsageExample\UIUsageExample.SLN,以便不会遇到有关丢失项目和引用的错误。两个解决方案文件都包括 Bugslayer.TestTools.GuiAutomation.CSPROJ 文件,后者包含所有核心代码。
您要检查的类是 UIWindow,因为这是 Bugslayer.TestTools.GuiAutomation 的所有内容的基础。它包装 AutomationElement 类,该类作为 RootElement 属性公开。几乎派生类的所有实际功能都可以在 UIWindow 中找到。例如,您将在 AutomationElements 上访问的所有最常见属性(例如,AutomationId、Name、IsEnabled 等等)都在这里。
子控件查找方法也在 UIWindow 上。在考虑开发人员会如何使用我的 API 时,我感到他们会对直接子控件并且偶尔对后代控件执行两个主要搜索。因此,我让许多 FindChild/FindDescendent 方法携带要查找的 AutomationId。其他重载还允许您指定确切的 ControlType,以便确定您正在查找所希望的确切控件。
AutomationId 值对国际化非常有用,但在搜索后代树时必须非常小心,因为值被重用。例如,菜单栏上的“文件”菜单有值“Item 1”,但展开“文件”菜单后,“打开”菜单项还将有值“Item 1”。
除了按 AutomationId 值搜索以外,我还支持用 FindChildByName 和 FindDescendentByName 方法按控件的本地化名称进行搜索。不管按什么进行搜索,我的 Find* 方法都是 AutomationElement.FindFirst 方法的包装程序,因此它们只返回与特定条件匹配的第一项。我选择这样做是因为:在驱动 GUI 应用程序时,您会提前知道用户界面中有哪些控件,因此通常您不想知道屏幕上有哪些控件。如果出于验证目的,您的确需要调用 AutomationElement.FindAll 方法以返回所有匹配的控件,则应利用 UIWindow.RootElement 属性搜索和排序所需的所有项。
如果阅读文档中关于查找控件的内容,就会看到对于在 RawViews、ControlViews 和 ContentViews 中进行搜索有详细讨论。UIWindow。默认情况下,Find* 方法对 Automation.ControlViewCondition 执行搜索。如果要更改为对特定 UIWindow 执行搜索,请将 TreeSearchCondition 设置为 Automation.ContentViewCondition、Automation.ControlViewCondition 或 Automation.RawViewCondition。
关于 Find* 方法我要提到的最后一点是,您可能希望通读 UIWindow 代码,因为它将显示我如何重构代码直至某些非常小的方法,以及如何在 Microsoft UI 自动化 API 中处理查找控件。
在 UIWindow 类中的大量工作是为控件模式提供受保护的包装程序。38 种不同控件类型使用很多控件模式,并且我希望在简单的包装程序属性和方法中隐藏所有访问和使用模式的细节。例如,菜单项控件类型可以实现 ExpandCollapsePattern。我的代码有 UIMenuItem 类,该类有 SupportsExpandCollapsePattern,用于调用受保护的静态 UIWindow.ImplementsExpandCollapse 方法。UIMenuItem 类有其余 ExpandCollapsePattern、ExpandCollapseState、Expand 和 Collapse 的公用方法和属性,如果 SupportsExpandCollapsePattern 返回 True 则可以调用它们。
通过让控件模式代码全部在 UIWindow 中实现,诸如 UIComboBoxControl(它也可能支持 ExpandCollapsePattern)这样的其他控件类型只需实现模式的公用方法。我的想法是只公开这些类支持的方法和属性,这样使用 API 就更为容易。
在设计 Bugslayer.TestTools.GuiAutomation 时我面对的大问题是确定如何让 UIWindow.Find* 方法返回确切的类型。例如,如果看一看 UIWindow.FindDescendant,它将返回 UIWindow。我希望我的实际自动化测试代码能够找到菜单项,并返回 UIMenuItem 类。这意味着,我必须向 UIWindow 类提前提供派生类的知识。虽然在面向对象的单词感知中这个想法不十分完美,但它确实有效,并且使 Bugslayer.TestTools.GuiAutomation 更容易使用。如果我不这样做,您就必须编写类似下面的代码:
UIWindow rawMenuBarWindow = notepadWindow.FindChild ( 
    "MenuBar" , ConwwtrolType.MenuBar );
UIMenuBar menuBarWindow = new MenuBar ( rawMenuBarWindow.RootElement );
无论何时 UIWindow 方法需要新建 UIWindow,它就要调用 UIWindow.AllocateExactType 方法,而该方法将接受 AutomationElement 作为唯一参数。在 AutomationElement 内部,AllocateExactType 会查看 ControlType 属性,并分配合适的派生类。通过执行准确的类类型分配,可以编写类似下面的代码,并在其所有受影响位置使用 as 运算符:
UIMenuBar menuBar = notepadWindow.FindChild ( 
    "MenuBar" ,  ControlType.MenuBar ) as UIMenuBar ;
如果向 Bugslayer.TestTools.GuiAutomation 添加其他控件类型,则必须确保在 AllocateExactType 中添加合适的分配项。任何时候,我都愿意用 API 易用性交换面向对象的完美性。
一个略微有趣的事情是输入文本。如果通读控件类型文档,您将看到只有少数控件类型支持获得和设置文本所需的 TextPattern 和 ValuePattern。我决定对这些特定控件所做的处理是建立 UITextControl 类,该类用于对静态文本控件建模,并充当所有支持 TextPattern 和 ValuePattern 的控件的基类。这样,功能被共享,而且我可以建立让 UIEditControl 和 UIToolTopControl 均从 UITextControl 派生的层次结构。此外,UIDocumentControl 派生于 UIEditControl。它支持代码重用,而且还能确保将正确的 ControlType 用于合适的类。
在 Microsoft UI 自动化 API 中,允许多行输入的任何控件都被视为 ControlType.Document。从文档角度说,永远不假定文档控件可支持 ValuePattern,这意味着无法设置这些控件的值。(TextProvider 将使您获得来自文档控件的数据。)这很有用,因为每个可支持比纯文本更复杂的数据的程序都会非常难以建模。但是,由于有时直接将文本注入文档非常有用,因此我将 System.Windows.Forms 中的 SendKeys 类包装到 UIWindow 中。我还在该类中放入相应处理,以确保在执行实际键盘输入之前,将焦点设置到已建模的窗口上。


 

UIMenuBar 类
我要讨论的最后一个有趣的帮助器是 UIMenuBar 类。使用 Microsoft UI 自动化 API 时,执行正常下拉菜单涉及查找菜单、展开它、查找合适子项并调用它。在图 2 中可以看到,仅仅是执行“文件”|“打开”菜单是很乏味的工作。为了避免如此麻烦的菜单处理,UIMenuBar.InvokeMenus 命令可以接受变长字符串参数,并在单个调用中执行常见工作。第一个字符串被假定为作为菜单栏的子项的初始菜单,而所有后续参数(除了最后一个)则是要展开的菜单。最后一个菜单通常是具有 InvokePattern 的菜单,因此 UIMenuBar.InvokeMenus 将调用它。从图 3 可以看到,它肯定能削减掉正常情况下必须编写的大量代码。


 

打包
既然您知道了我考虑的是在 Bugslayer.TestTools.GuiAutomation 中易用的库,那么我希望您仔细考虑如何将您的用户界面自动化绑定到单元测试中。像涉及测试代码的其他任何事情一样,您需要将它视为像产品那样的提交结果,但回报是巨大的,因为代码质量实现了飞跃。
如果要了解如何使用 Bugslayer.TestTools.GuiAutomation 的其他想法,请参见 Bugslayer.TestTools.GuiAutomation\Tests\Bugslayer.TestTools.GuiAutomation.Tests 目录中的单元测试。这些测试使用了这个库的所有方面,以便对其进行测试。作为代码覆盖率的忠实信徒,我骄傲地报告现有单元测试的命中率超过了代码的 90%(在 Windows XP 上)!如果您有合适的 Visual Studio 版本,请运行这些测试,因为看到五个或六个不同的记事本实例同时自动运行是很刺激的(测试按随机顺序运行,因而可以交错)。
我要请您记住关于如何扩展 Bugslayer.TestTools.GuiAutomation 的一些想法。正如您在代码中看到的,您会发现我尚未对所有控件类型建模。请按照我已建立的开发模式添加其余控件类型。如果您添加其他控件类型,请确保添加合适的单元测试。
在 Microsoft UI 自动化 API 中缺少鼠标支持是很多应用程序的一个问题。请考虑向 UIWindow 添加 PlayMouse 方法,其中携带 SendKeys 格式的字符串,并使用 SendInput API 执行操作。可以在 Tester 中查看我是如何实现 PlayKeys 命令的,以获得建立和分析 SendInput 数组的帮助。
如果您确实很感兴趣,可以建立一个记录器,通过使用 Microsoft UI 自动化 API 中出色的事件支持功能,用该记录器来监视您与应用程序的交互。在交互中,可以创建可以进行编译和测试的 C# 或 Visual Basic® 源文件。
如果您创建了上面的任何一项,请让我知道,我将很高兴协调在 Bugslayer.TestTools.GuiAutomation 上的工作。


 

提示
提示 77 我喜欢 Team Foundation System,但几天前却遇到一个大问题。我不得不将工作从一个项目转移到另一个项目。正当我欲哭无泪的时候,考虑到我要手动完成每项工作,Eric Lee 前来营救我,他用他的 Hemi 工具,只是单击了几下鼠标,就实现了工作在两个项目之间的转移。请在 blogs.msdn.com/ericlee/archive/2006/11/20/work-item-moving-tool-is-back.aspx 上了解如何使用 Hemi 并下载这个精彩的免费工具。

Tags:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5