QTP自动化测试实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第3章 软件自动化测试工具

软件自动化测试工具是实现软件自动化测试必不可少的关键,因此,选择一个优秀的、适合自己的测试项目实际情况的测试工具是实现成功自动化测试的第一步。本章介绍自动化测试工具的分类,以及如何选择一个合适的自动化测试工具,并且介绍自动化测试工具的基本原理。

3.1 自动化测试工具类型

测试工具的种类很多,有用于管理测试的,有帮助实现测试自动化的,有开源的,有免费共享的。软件测试工具按照其用途,可大致分成以下几大类:

❑ 测试管理工具

❑ 自动化功能测试工具

❑ 性能测试工具

❑ 单元测试工具。

❑ 白盒测试工具。

❑ 测试用例设计工具。

如果按测试工具的收费方式,又可分为以下几类。

❑ 商业测试工具。

❑ 开源测试工具。

❑ 免费测试工具。

3.1.1 商业测试工具

商业测试工具的特点是需要花钱购买,但是会相对成熟和稳定,并且有一定的售后服务和技术支持。但是,由于其价格昂贵,并不是每一个企业都能负担得起。

商业测试工具主要集中在GUI功能测试和性能测试方面,目前流行的基于GUI的功能自动化测试工具有Robot、QTP、TestComplete等。各种自动化测试工具实现的功能基本相同,但是在IDE、脚本开发语言、支持的脚本开发方式、支持的控件等方面则有很多不同之处。

3.1.2 开源测试工具

开源软件是指软件的源代码是公开发布的,通常是由自愿者开发和维护的软件。开源测试工具是测试工具的一个重要分支。越来越多的软件企业开始使用开源测试工具。但是开源并不意味着完全的免费,开源测试工具同样需要考虑使用的成本,并且在某些方面可能要比商业测试工具的成本还要高。

商业工具的价格在不断地提高。图3.1为WinRunner近几年的价格变化图。

图3.1 WinRunner近几年的价格变化

可以看到,价格在不断地增长。这对于那些中小型软件企业而言,无疑加大了测试的成本。开源测试工具相对于商业测试工具拥有以下优势:

相对低的成本:大部分开源测试工具可免费使用,只要不做商业用途即可。

更大的选择余地:可以打破商业测试工具的垄断地位,给测试人员更多的选择空间。

可自己改造:源代码开放,意味着可对其进行修改、补充和完善,可对其进行个性化改造。

虽然开源测试工具拥有一定的优势,但是,同时也存在很多不足之处,包括以下几方面。

安装和部署相对困难:大部分开源测试工具的安装配置过程比较烦琐,需要测试人员付出一定的努力。

易用性:开源测试工具在易用性、用户体验方面做得不够完善。

稳定性:部分开源测试工具的稳定性不够强。

学习和获取技术支持的难度:大部分开源测试工具不提供培训指导和技术支持服务,联机帮助和用户手册不够完善,增加了测试人员的学习难度。

3.1.3 自主开发测试工具

目前,很多软件测试组织其实已经具备了自己动手开发测试工具的条件:

❑ 市场对于测试工具的接受程度在不断提高,人们对测试工具的认识不断加强和深入,对测试工具原理的理解不断提高。从脚本化到数据驱动,再到关键字驱动等,很多新的测试工具理念被引入并被广泛接受。

❑ 由于技术的成熟,测试工具变得容易构建。软件系统现在变得更容易测试,可测试性更强,COM、XML、HTTP、HTML等标准化的接口使得测试更加容易进行。托管程序(例如Java、.NET)的反射机制使得查找定位对象,以及捕捉对象和操作对象更加容易。

❑ 一些开源的框架可以被利用。利用开源框架平台来组合、搭建适合自己测试项目使用的测试平台和测试框架。

自己动手开发测试工具的优势有以下方面。

❑ 购买成本为零。

❑ 简便:只需要开发自己需要的那部分功能。

❑ 个性化:可自己定制需要的功能,随时修改,配置项目组成员的使用习惯。

❑ 可扩展性:可随时增加新的功能。

❑ 可充分利用项目组熟悉的语言开发,利用自己的技术优势。

❑ 可使用自己熟悉的脚本语言,不需要使用商业工具提供的“厂商脚本语言”。

然而,虽然自己动手设计和开发测试工具有很多好处,但是必须考虑随之而来的成本问题。自己开发测试工具的成本只是开发时间和人员投入的成本,以及维护的成本。当然,如果把测试工具推广到其他项目组,则也会有学习和培训成本。另外,需要考虑测试工具的实用性,不要做一个大而全的、面面俱到的、很多功能基本上不会被用到的测试工具。

3.2 自动化测试工具选型

为了保证在一个测试团队中成功地应用某款测试工具,尤其是对于大型商业工具的应用,应该首先进行工具的选型,通过分析实际情况,确定选用范围。对选用范围内的几款测试工具进行试用。根据试用的反馈效果决定最终采用哪款测试工具。在大规模使用工具之前,还应该对测试人员进行全面的工具培训。培训后,正式在项目中应用测试工具,制定相应的测试工具使用策略,并把工具融入测试工作中。

3.2.1 测试工具评估

测试工具的选型是成功应用测试工具的第一步,测试工具的选型应该注意以下几点:

(1)首先,分析项目的特点,软件系统采用的开发工具、语言、技术、平台等。还要结合测试的类型、测试的要求。

(2)同时还要了解目前存在的各种测试工具的情况,包括工具的生产厂家、价格、产品特性、技术支持和售后服务情况,还要了解该工具的市场占有率、使用人群等情况,如果是国外厂商生产的测试工具,最好再了解清楚国内的代理机构的情况等。

(3)选型的最后一步是编写选型报告。通过综合分析所有收集回来的材料,横向比较测试工具的优势和劣势。

3.2.2 测试工具试用

在初步选型后,可定出几个满足要求的测试工具,然后进行深入的试用工作,应该尽可能尝试测试工具的所有功能,并且可能的话,要尽量在项目的软件系统中尝试。

需要制定一份详细的测试工具的试用计划,因为这可能是一项长时间的、需要谨慎进行的工作,尤其是对于那些商业的测试工具,动辄上百万的购置费用。很多公司由于没有谨慎进行前期的选型和试用工作,导致购买的测试工具不适用,或者使用效果不理想,最后被测试人员扔在角落里。

注意

不要仅仅听信测试工具销售人员的介绍就轻易购买,一定要自己组织一次详细的试用活动,确认适合在项目中使用,才能购买。

3.2.3 自动化测试工具的培训

确定了选用的测试工具后,正式在测试项目中使用该测试工具之前,还需要组织相关测试人员进行测试工具的培训。测试工具的培训可包括以下内容。

(1)测试工具的总体介绍主要给测试人员讲解测试工具包括哪些主要的功能和特性,可用于哪些方面的测试。

(2)测试工具操作方法介绍主要给测试人员讲解测试工具的每一项功能的使用方法、操作步骤、注意事项等方面的内容。一般可由工具厂商派遣的技术支持人员进行,也可由熟悉该工具的测试人员来介绍,例如,负责前期测试工具试用的测试人员。

(3)测试工具使用实践,则是结合某个具体的例子给测试人员演示测试工具的使用方法和使用经验等。一般可由负责该测试工具试用的测试人员进行。

(4)对测试工具相关的测试理论进行讲解的目的是为了让测试人员了解该测试工具的原理,以及工具所应用的领域的相关理论知识,让测试人员在理论知识的指导下能更好、更恰当、更充分、更正确地使用测试工具。

技巧

测试工具的培训是成功引入测试工具的关键环节,在正式使用测试工具之前,应该确保测试人员充分掌握测试工具的基本使用方法,避免在使用过程中碰到很多工具操作和使用上的问题,导致测试进度缓慢。

3.3 自动化测试工具的原理

测试工具的优势在于可部分地替代人工的测试过程,能重复不断地执行,能精确判断数值和字符对象。自动化测试工具把测试用例用自动的方式执行,例如,自动地产生数据,自动地打开应用程序,自动地查找控件,自动地输入数据,自动地操作控件,自动地收集测试结果,自动地与预期结果进行比较等。

自动化功能测试工具可基于GUI层面进行测试,也可基于代码层面进行测试。只要实现了自动化执行测试用例,自动化地检查测试数据的测试工具,替代人工进行测试步骤的执行,从而验证应用程序是否满足了特定功能的测试工具,都可以称为自动化功能测试工具。

3.3.1 基于代码层面的功能自动化测试工具

基于代码层面的功能自动化测试工具主要是一些单元测试工具,例如JUnit、NUnit、MSTest等,这些工具直接访问被测试的应用程序的代码,对其中的类和函数进行调用,输入各种测试数据,检查函数的返回值,通过比较返回值与期待的值是否一致来判断测试是否通过。图3.2所示的是Visual Studio.NET 2005中的单元测试管理界面。

图3.2 Visual Studio.NET 2005中的单元测试管理界面

这种类型的工具主要实现了测试代码框架产生的自动化,例如,下面代码是Visual Studio.NET 2005中的单元测试框架MSTest为某个类的方法自动产生的单元测试代码框架:

        // 以下代码由 Microsoft Visual Studio 2005 生成。
        // 测试所有者应该检查每个测试的有效性。
        using Microsoft.VisualStudio.TestTools.UnitTesting;
        using System;
        using System.Text;
        using System.Collections.Generic;
        using AUT;
        namespace TestProject1
        {
            /// <summary>
            ///这是 AUT.Form1 的测试类,旨在包含所有 AUT.Form1 单元测试
            ///</summary>
            [TestClass()]
            public class Form1Test
            {
          private TestContext testContextInstance;
          /// <summary>
          ///获取或设置测试上下文,上下文提供有关当前测试运行及其功能的信息。
          ///</summary>
          public TestContext TestContext
          {
              get
              {
                  return testContextInstance;
              }
              set
              {
                  testContextInstance = value;
              }
          }
          #region 附加测试属性
          //
          //编写测试时,可使用以下附加属性:
          //
          //使用 ClassInitialize 在运行类中的第一个测试前先运行代码
          //
          //[ClassInitialize()]
          //public static void MyClassInitialize(TestContext testContext)
          //{
          //}
          //
          //使用 ClassCleanup 在运行完类中的所有测试后再运行代码
          //
          //[ClassCleanup()]
          //public static void MyClassCleanup()
          //{
          //}
          //
          //使用 TestInitialize 在运行每个测试前先运行代码
          //
          //[TestInitialize()]
          //public void MyTestInitialize()
          //{
          //}
          //
          //使用 TestCleanup 在运行完每个测试后运行代码
          //
          //[TestCleanup()]
          //public void MyTestCleanup()
          //{
          //}
          //
          #endregion
          /// <summary>
          ///Add(int,int) 的测试
          ///</summary>
          [DeploymentItem("AUT.exe")]
          [TestMethod()]
          public void AddTest()
          {
              Form1 target = new Form1();
              TestProject1.AUT_Form1Accessor accessor =
                                        new TestProject1.AUT_Form1Accessor(target);
              int i=0;//TODO: 初始化为适当的值
              int j=0;//TODO: 初始化为适当的值
              int expected = 0;
              int actual;
              actual = accessor.Add(i, j);
              Assert.AreEqual(expected,actual,"AUT.Form1.Add 未返回所需的值。");
              Assert.Inconclusive("验证此测试方法的正确性。");
          }
       }
     }

在代码框架的背后,单元测试框架负责查找和调用被测试的类和方法,通过代码反射机制可以访问到被测试代码中的所有方法和属性。另外,单元测试框架会提供一系列的Assert类,使用这些Assert类可以简化测试结果检查、判断的工具。

提示

在执行单元测试时,单元测试框架负责加载包含测试类的程序集文件,通过查找里面的测试类和测试方法标识来加载测试方法,例如,上面代码中的“[TestMethod()]”就是用于标识其中的测试方法。

3.3.2 基于浏览器和DOM对象模型的功能自动化测试工具

另外一种自动化的功能测试工具是基于浏览器和DOM对象模型开发的,例如Selenium、Watir等,这些测试工具直接访问Web浏览器,利用脚本语言操纵浏览器和Web页面中包含的DOM对象,从而达到模拟用户控制浏览导航、页面元素的操纵等效果,并且直接获取DOM对象的属性,从而获得Web页面元素的各种属性,通过这些属性可判断测试步骤的结果是否正确。图3.3所示的是可作为插件嵌入到Mozilla Firefox浏览器中的Selenium IDE的测试界面。

图3.3 Selenium IDE的测试界面

HTML DOM(Document Object Model)是一个HTML文档的编程接口,它定义了HTML的标准对象集合,并且定义了标准的访问和操纵HTML对象的方式。HTML DOM接口让测试人员可以访问和操纵HTML文档的内容。图3.4所示的界面是使用了一个名为“IE DOM Inspector”的工具查看到的Web页面中的DOM对象。

图3.4 IE DOM Inspector的界面

如果熟悉和了解DOM的原理,那么完全可以自己动手编写一个基于浏览器和DOM的Web页面自动化测试工具,例如,下面的C#代码就是一个简单的例子:

        using System;
        using System.Collections.Generic;
        using System.ComponentModel;
        using System.Data;
        using System.Drawing;
        using System.Text;
        using System.Windows.Forms;
        using System.Diagnostics;
        using System.Threading;
        // 引用Microsoft.mshtml的HTML接口
        using mshtml;
        // 引用IE对象
        using SHDocVw;
        namespace WebAutomatedTest1
        {
            public partial class Form1 : Form
            {
          static AutoResetEvent documentComplete = new AutoResetEvent(false);
          public Form1()
          {
              InitializeComponent();
          }
          private void button1_Click(object sender, EventArgs e)
          {
              InternetExplorer ie = null;
              // 启动IE的进程
              Process p = Process.Start("iexplore.exe", "about:blank");
              // 等待一段时间,让IE启动
              Thread.Sleep(3000);
              if (p == null)
              {
                  MessageBox.Show("不能启动IE!");
                  return;
              }
              SHDocVw.ShellWindows allBrowsers = new SHDocVw.ShellWindows();
              // 附加到IE进程
              int i = 0;
              while (i < allBrowsers.Count && ie == null)
              {
                  InternetExplorer browser = (InternetExplorer)allBrowsers.Item(i);
                  if (browser.HWND == (int)p.MainWindowHandle)
                                  ie = browser;
                  ++i;
              }
              if (ie == null)
              {
                  MessageBox.Show("不能附加到IE!");
                  return;
              }
              ie.DocumentComplete +=
        new DWebBrowserEvents2_DocumentCompleteEventHandler(ie_DocumentComplete);
              object nil = new object();
              ie.Navigate("http://127.0.0.1:9462/WebUT/default.aspx",
              documentComplete.WaitOne();
                                            ref nil,ref nil,ref nil,ref nil);
              HTMLDocument Doc = (HTMLDocument)ie.Document;
              HTMLInputElement textBox =
                      (HTMLInputElement)Doc.getElementById("TextBox1");
              textBox.value = "123";
              HTMLInputElement button =
              button.click();
                      (HTMLInputElement)Doc.getElementById("Button1");
              // 验证,如果Label1的值等于123,则表示测试通过
              HTMLSpanElement label =
                        (HTMLSpanElement)Doc.getElementById("Label1");
              if (label.innerText == "123")
              {
                  MessageBox.Show("测试通过!");
              }
              else
              {
                  MessageBox.Show("测试不通过!");
              }
          }
          private static void ie_DocumentComplete(object pDisp, ref object URL)
          {
              documentComplete.Set();
          }
       }
     }

3.3.3 基于GUI对象识别的测试工具原理

目前,大部分自动化功能测试工具,尤其是商业的测试工具,都是基于GUI对象识别技术来设计的。基于GUI层面的测试需要与各种界面元素打交道,而且不同的编程语言和开发工具开发的应用程序在界面的表现、事件的响应上都略有不同,因此,设计基于GUI层面的自动化功能测试工具会更为复杂些。

Windows的API中封装了很多可用于自动化测试编程的函数,例如FindWindow、GetWindowRect等函数。这些函数可在编程语言或脚本代码中进行调用,从而实现自动化测试编程。例如,下面的C#代码封装了user32.dll中的FindWindow函数,实现查找指定类型和名称的控件的功能:

        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Runtime.InteropServices;
        namespace LowLevelGUITest
        {
            public class Automation
            {
          // 引入user32.dll中的FindWindow函数
          [DllImport("user32.dll", CharSet = CharSet.Auto)]
          static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
          // 查找控件方法
          public IntPtr FindControl(string ClassName,string ControlName)
          {
              // 返回控件句柄
              return FindWindow(ClassName, ControlName);
          }
       }
     }

可用下面的C#代码来调用这个封装的函数:

            private void button1_Click(object sender, EventArgs e)
            {
          Automation automation = new Automation();
          // 查找名为AUT的窗体
          IntPtr p =
          automation.FindControl("WindowsForms10.Window.8.app.0.378734a","AUT");
          // 判断是否找到控件
          if (p == IntPtr.Zero)
          {
              Console.WriteLine("找不到指定的控件!");
          }
        }

如果想进一步了解GUI对象识别的原理,可以打开Visual Studio.NET 2005自带的Spy++,如图3.5所示。

图3.5 Spy++的界面

Spy++中的搜索查找控件功能就是这种对象识别原理的体现。例如,在图3.6所示的界面中,可以看到某个指定的窗口的句柄、标题、类。

图3.6 Spy++中的搜索查找控件功能

3.3.4 反射机制在自动化功能测试工具中的使用

在基于GUI对象识别和控制的自动化测试工具中,过去一直依赖于Windows API函数的调用。而随着新的编程语言和平台的出现,涌现了很多新的语言特性,这些语言特性可用于自动化测试工具的设计,例如反射机制就是其中一项技术。

提示

程序集包括模块,模块包含类型,类型包含成员。在.NET和Java中,都提供了反射的机制,反射提供了封装程序集、模块和类型的对象,可以通过反射动态地创建类型的实例,将类型绑定到现有对象,或者从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。

反射机制可被用在测试中,通过反射来加载被测试程序,获取被测试程序的各种属性,触发被测试程序的各种事件,从而达到自动化测试的目的。例如,下面的C#代码通过反射机制读取程序中textBox1控件的Text属性:

          // 获取控件属性
          static object GetControlPropertyValue(string controlName,
                                                      string propertyName)
          {
              if (AUTForm.InvokeRequired)
              {
                  Thread.Sleep(1000);
                  return AUTForm.Invoke
                                  (new GetControlPropertyValueHandler(GetControlPropertyValue),
                                  new object[] { controlName, propertyName });
              }
              // 获取类型
              Type t1 = AUTForm.GetType();
              // 获取类型中的成员
              FieldInfo fi = t1.GetField(controlName, BindingFlags.Public |
              BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
              object ctrl = fi.GetValue(AUTForm);
              Type t2 = ctrl.GetType();
              // 获取成员中的属性
              PropertyInfo pi = t2.GetProperty(propertyName, BindingFlags.Public |
              BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
              // 返回控件属性值
              return pi.GetValue(ctrl, new object[0]);
          }
          delegate object GetControlPropertyValueHandler(string controlName, string
        propertyName);

而下面的C#代码则通过反射机制调用控件的方法,模拟用户点击按钮的过程:

          // 模拟用户点击按钮
          InvokeMethod("button1_Click", new object[] { null, new EventArgs() });
          // 调用控件方法
          static void InvokeMethod(string methodName, params object[] parms)
          {
              if (AUTForm.InvokeRequired)
              {
                  Thread.Sleep(1000);
                  AUTForm.Invoke(new InvokeMethodHandler(InvokeMethod),
                                          new object[] { methodName, parms });
                  return;
              }
              // 获取类型
              Type t = AUTForm.GetType();
              // 获取类型中的指定方法
              MethodInfo  mi  =  t.GetMethod(methodName,  BindingFlags.Public  |
        BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
              // 调用方法
              mi.Invoke(AUTForm, parms);
          }
    delegate void InvokeMethodHandler(string methodName, params object[] arms);

3.3.5 UI Automation在自动化功能测试工具中的使用

随着.NET 3.0和Vista的发布,微软提供了一套崭新的Windows界面框架,名为“UI Automation”,UI Automation在所有支持Windows Presentation Foundation (WPF)的操作系统中都可以使用。

UI Automation提供了一套对Windows界面的可编程的访问机制,同时也为测试自动化工具提供了一个新的思路。例如,在使用QTP的.NET插件对WPF控件进行测试时,就可以使用AutomationElement来访问控件属性,再例如,下面脚本通过AutomationElement访问Test4窗体控件的Name属性:

        '通过AutomationElement访问控件属性
        msgbox WpfWindow("Test4").AutomationElement.Current.Name

3.3.6 QTP对象识别技术

有了前面的基础,就不难理解QTP的工作原理了。QTP同样是通过查找应用程序界面中的各个控件的属性来判断是否与测试对象匹配,例如QTP中提供的“Object Spy”功能,就可以把某个界面控件的基本属性列举出来,如图3.7所示。

另外,还可以根据控件的类型,把其拥有的可操作的方法列举出来,如图3.8所示。

针对不同平台和语言编写的控件,QTP会采用不同的对象识别方法,但是共通的一点是,通过查找对象属性,依据该控件与其他控件能区分的属性来判断其身份,例如控件的类名、控件的文本等。QTP中的“Object Identification”界面中提供了一个可供测试员调整的对象识别配置器,如图3.9所示。

图3.7 QTP的“Object Spy”功能

图3.8 使用“Object Spy”查看控件可操作的方法

图3.9 QTP的“Object Identification”界面

提示

其中“Mandatory Properties”中的属性是强制性的判断准则,“Assistive Properties”中的属性用于辅助性地判断。

单击“Add/Remove”按钮,可在如图3.10所示的界面中添加更多的属性用于唯一地标识和区别某个控件实例。

图3.10 添加属性