Hi, 如果有任何想法与我沟通, 请用: lifr_nj 在 msn.com

发布新日志

  • Different QTP: 后记

    2012-09-08 22:39:24

    后记

    QTP软件测试开发应该是什么样的?怎样才是高效的开发方式?QTP软件测试只需要三个月的培训就能做好吗?

    在开始进入QTP自动化测试开发之前和之中, 我有这样一些疑问。 慢慢的, 我体会到自动化测试开发和其他通用的软件开发并无根本之不同。 也就是说软件开发中你能看到的一些概念,比如: 模块化, 低耦合, 面向对象, 敏捷, 设计模式, 可维护性, 接口设计。。。在QTP自动化测试开发中同样适用。 没有软件开发的基本素养, 同样也不能开发好一个QTP自动化测试项目(我承认,或许能够开发出来, 但可维护性会很糟糕)。

    但是, 因为从事QTP自动化测试开发的人员很多都是QA而不是Developer 所以QTP提供了很多方便的智能的工具来降低上手的难度, 并且打出了”不会程序设计也可以做QTP自动化测试”的口号(这种现象其实在别的开发领域也有, 比如“三十天掌握J2EE”等等)但是, 这些工具虽说是捷径但却牺牲了软件开发中一些基本的要素。简单来说, 这些工具像积木, 可以搭房子,但不能建立大厦。所以, 我慢慢的抛弃了这些工具, 而转向了我所熟悉的, 通常的软件开发方式来开发QTP自动化测试项目。

    实践也证明了, 通常的软件开发方式也完全可以开发QTP自动化测试项目, 而且开发效率和可维护性还更高(至少对我而言)。

    到此为止, 我对于QTP自动化测试开发的绝大部分想法和经验都和你分享了。希望能对你有所启发和帮助。(DFL, 2012/09/07)

  • Different QTP: QTP使用中的陷阱

    2012-09-08 22:35:31

    QTP使用中的陷阱

    不要使用Reuable Action

    Function 不要用Reusable Action 没有一种通用的语言里有Reusable Action这个概念。 而且通过Function等一些标准的程序设计语言的元素, 你能够实现任何Reusable Action可以实现的功能, 而且更好, 更快, 更易于维护。

    以前我还不能肯定这一点。 现在我能肯定的告诉你, 因为有好几个QTP项目, 上千个Testcases在支持我的观点。

    不要用Smart Identification

    有一天, 我发现一个奇怪的现象, 一个testcase里某一个点击logout button的步骤运行非常慢, 大概要20秒,但是最终它还能成功点击。 不巧的是每一个testcase几乎都会点击这个button 所有我还必须把这个问题找出来。 最后发现这是因为buttonname有了变化, 但是因为Smart Identification是被enable 所以QTP会试图去适应这个变化, 但是这个“适应”的效果非常不理想。

    我认为测试开发者应该完全控制对象的识别。把选择权交给对被测程序业务一无所知的工具是毫无道理的。我想不到任何使用SmartIdentification的原因。 所以,从那之后, 任何TestcaseSmart Identification我都禁止了。

    不要在base目录里添加两个或以上目录

    Base目录是用来只能识别相对路径的目录。其配置在Menu: Tools->Options->Folders。我的建议是这里只放项目根目录。其他目录都不要放进去。

     曾经, 我接手了一个QTP项目。 开始的时候我根本就不能把哪怕一个testcase成功跑起来。于是我去问起开发者, 他告诉我需要把某一个特定的Reusable Action添加到"folders"里面。 这种坑是在是让人哭笑不得。

    QTP作为一种工具, 或许需要提供这种灵活性。但我们除非有必要, 不要去用它。 如果必须要用, 也要很好的document这点。

    不要用keyword view 而是提供业务逻辑封装层

    如果你要让你的testcase简单, 直接,那么你应该通过合理的抽象提供完善的业务逻辑封装层, 它会使得你的testcase script读起来像testcase descript一样。 这个时候, 你根本不需要keyword view

    不用Reporter.ReportEvent 而用Assert

    当执行一个Testcase时候, 如果遇到错误, QTP支持两种操作。"stop run" "proceed to next step"。(注意, 其实还有另外两个选项。 "pop up message box"是针对开发时使用。 "proceed to next action iteration"不常用。 所以不讨论。)

     

    基本上, QTP鼓励"proceed to next step"这种方式:如果出现了错误, 那么记录错误, testcase仍旧会执行下去。

    对于QTP检查到的错误是这样, 对于测试脚本自己检查到的错误也是如此。 QTP提供了一个错误报告机制。

    Reporter.ReportEvent micFail, stepName, errorMsg

    这种处理方式违背了一个程序设计的基本原理:如果出现不能处理的错误, 那么就抛出错误。通常来说如果出现了错误, 执行下去是没有意义的。 我猜想QTP之所以会支持这种方式是因为, 如果出现错误马上退出, 那么testcase里的负责cleanup的代码就无法执行。 想想看JUnit里的SetupTeardown机制, VBS里是无法实现的。

    即使考虑到这点, 这样做还有一个问题,导致我不采用它的原因。 那就是不能在batch run的结果报告里显示错误。 如果采用"stop run"的方式, 那么batch run脚本能够获得导致testcase退出的错误信息。 然后在产生report的时候, 能够呈现出来。 再结合错误时刻的screenshot 实践表明, 大多数错误都能在阅读report的时候确定原因。 下面是一个Report里一个失败Testcase的呈现情况。

    如果使用Reporter.ReportEvent的方式, 那么在Report里你看不到任何错误消息,只能在Result View里面打开result xml。这是一个耗时的操作,因为Result View很慢。 大多数时候,你是在等待Result View把测试结果渲染出来。

    为了更好的支持checkpoint 我在framwork里定义了一套Assert函数, 可以对大多数情况下的断言语义提供直接的支持, 比如“相等”, “不等”, “包含”, “匹配”, 等。在Assert函数里面, 会抛出一个Error 下面是一个从popup窗口读取信息, 然后验证信息是否正确的例子。

    AssertMatch GUI_MsgBox_Msg("Information"),_

        ".*is invalid or you don't have the permission.", _

        "Error message not displayed or correct"

    如果该Checkpoint失败, 那么在HTML report里看到的信息像下面的样子。

    AssertMatch: Error message not displayed or correct. Expected<,".*is invalid or you don't have the permission.>, Actual<>

    总结一下。在错误处理上,采用"stop run"的方式, 这也是所有程序设计里采用的方式。 在写checkpoint的时候,用Assert而不要用Reporter.ReportEvent, 因为前者会帮助你更快的分析错误。

  • Different QTP: Project目录架构

    2012-09-08 22:27:10

    Project目录架构

    在安排QTP test Project的目录树的时候,需要考虑到下面的问题

    ·         如何组织Function Library, 如何引用function library文件?

    ·         如何组织Testcase, 如何在testcas间共享数据/代码?

    ·         如何配置静态参数, 运行时环境和全局测试数据?

    如何组织Function Library

    按照前面的设计方法搭建的Function Library会包含大量的函数, 最终代码量也会比较庞大(基本上取决于业务逻辑封装函数的多少)。比如最大的一个项目, Function Library总共代码量达到11000多行, 假设1/4是注释, 那么实际代码行数 大约是8000多行, 按照一个函数20行代码, 总共大约400多个函数。

    为了维护这么多函数, 肯定要分成多个文件, 按照Module组织起来。 但是QTP对于外部文件引用支持很差。 基本上10个以上的外部文件引用就让人抓狂了。

    所以我采用了一种折中的方法。 首先, 一个Module对应一个vbs文件, 这样代码维护最方便。 然后有一个脚本能把所有的Module文件合并为一个大的qfl文件, 方便QTP引用。

    下面是一个真实项目的Library目录。

    ·         BIZ

    业务逻辑封装函数文件所在目录。

    ·         FRM

    VBS 运行时扩展库文件所在目录。因为VBS的运行时库实在是太简陋(当然, 这个也是情有可原, 毕竟是70年代的语言), 所以必须要写一些基本的扩展例程。 比如断言机制, Log机制, 动态数组, 动态字符串, 文件处理, 时间格式化, 等等。 接口基本上参照JVM做可以了 :)。

    把这些基本功能作为VBS扩展库, 和项目的Function Library分开有助于你维护一个跨项目的VBS function Library 更有效的利用代码。

    所以, 我的项目里, testcase script向下有下面的层次

                    +----------------------------------------+

                    |                                        |

                    |            TestCase Script             |

                    |                                        |

                    +---------------------------------------->

                    |                                        |

                    |    Function Library (BIZ, GUI, UTI)    |

                    |                                        |

                    +---------------------------------------->

                    |                                        |

                    |          VBS Extension Library          |

                    |                                        |

                    +----------------------------------------+

    ·         GUI

    GUI封装库所在目录。除了前面介绍的GUI元素库用来定位一个GUI元素, 还有对常用的GUI元素操作封装的函数。 比如下面是一个对Java Tree的封装。

     

    ·         Screen

    是所有Screen定义文件所在的目录。

    ·         UTI

    其他功能函数所在的目录。

    ·         AllInOne_Lib.bat AllInOne_Lib.qfl

    AllInOne_Lib.bat把所有上面目录里的vbs文件合并到AllInOne_Lib.qf里, 方便QTP引用。

    ·         INIT.qfl

    初始化函数库里Module的函数所在文件。因为某些Module的初始化可能有依赖关系, 所以提供一个专门用来初始化函数库的地方。

    ·         Library.cfg

    配置函数库里Module的配置文件. 这个文件内容必须满足VBS的语法.因为他也会合并到AllInOne_Lib.qfl里。在实践中我发现, 绝大多数Module其实都不需要外部配置, 因为毕竟这只是一个测试项目的Library,对灵活性的要求远没有开发产品的library那么高。

    如何组织TestCase?

    TestCase被组织在一个四层树状结构列. 如下所示.

           +-------------+

           | TestRepo    +-----> Test.qfl

           +-+-----------+

             |

             |     +---------------+

             +---->| TestModule    +-----> TestModule.qfl

                   +--+------------+

                      |

                      |  +----------------+

                      +->| TestSuite      +------> TestSuite.qfl

                         +--+-------------+

                            |

                            |  +-----------------+

                            +->| TestCase        |

                               +-----------------+

     

    最顶层是TestRepo(TestCase Repository Root)。下面是TestModule, 是针对产品的一个功能模块的所有testcase的集合。再下面是TestSuite,一般是针对一个特定的测试点的所有testcase的集合。最下面是TestCase

    对于Testcase组织来说, 有两个重要的问题,

    1)      如何在TestCase之间共享数据和函数

    对于第一个问题,每一个TestRepo TestModuleTestSuite都有一个公共库文件。 这个文件会被它所包含的所有Testcase引用。 所以在这个文件里, 一般配置特定范围的, 公共数据和公用方法。 举个例子: 如果某一个Testsuite里的 部分/全部TestCase都依赖于一个特定的对象。 那么可以把这个对象的数据, 比如名字, 配置在TestSuite.qfl里。因为此TestSuite里所有TestCase都引用此文件, 所以数据得以共享。

    对于任意一个Testcase 它的Resource引用都像下面的样子。

    ·         Library\AllInOne_Lib.qfl

    ·         ..\..\..\Test.qfl

    ·         ..\..\TestModule.qfl

    ·         ..\TestSuite.qfl

    ·         Library\INIT.qfl

     

    2)      如何实现SetupCleanup

    对于这个问题, 通过增加两个特殊的TestCase 00_Setup99_Teardown来解决。一个TestSuite里如果有这两个TestCase 那么00_Seup会被最先执行, 99_Teardown会被最后执行。 下面是一个实例。

     

    如何配置运行时环境

    对于一个QTP Test Project 环境数据必须是可配置的,而不是绑定到代码里。

    我使用了QTP风格的Environment配置文件, 方便在运行时被Load进来。 QTPEnvironment配置文件是XML格式,位置在 Conf/Environment.xml, 示例 如下

    <Environment>

       <Variable>

          <Name>ServerIP</Name>

          <Value>172.17.68.40</Value>

       </Variable>

    </Environment>

     

    在运行时, 通过Framwork加载此文件, 然后就可以用Environment.Value("ServerIP")的形式在代码里直接获取相应变量的值。

    如何把测试数据和测试逻辑分离?

    测试数据和测试逻辑分离是一句在自动化测试领域流程甚广的一句话。 但有时候它被过于强调, 成了QA对外宣传的口号, 于是它就成了政治人物. 比如对于QTP Project 有一种做法是定义一个外部的,集中式的的Testdata文件(Excel文件)。但是因为各个TestCase对数据种类的要求是不一样的, 把所有这些数据强行统一在一张sheet里的结果就是导致大量的column 我见过在Excel里面用到“T”这个column,也就是用20Column,简直是维护的灾难。

    基本上, 最适合测试数据和逻辑分离的是这样一个场景。 某一批testcase具有类似的逻辑, 于是把这个逻辑实现为代码, 而用testdata来表达其每一个testcase的不同点。但是事实是, 对于QTP Project 这样的情况并不多。 特别有些testcase看上去好像测试逻辑差不多, 但是从实现上来看, 差别很大。 如果强行统一, 那么测试数据会比较复杂, 增加开发和维护的负担。

    但是对于QTP Project, “测试数据和测试逻辑分开”其实也是被遵守的。那就是在编写一个具体的testcase的时候,

  • Different QTP: 测试执行流程与Mngt Tools

    2012-09-08 22:16:10

    测试执行流程与Mngt Tools

    一套测试套件设计出来之后, 它的价值在一遍一遍的执行中体现出来。如何能最大化的优化执行的流程, Mngt tools 的任务。 在我的项目中, Mngt Tools是由一些VBS脚本和BAT脚本构成。

    下图是Mngt Tools所包含的脚本。用户直接调用的脚本都是BAT脚本, 实际实现在VBS脚本里。VBS脚本放在Script文件夹里面, 如下图所示。

    测试项目的执行流程定义如下。

                             +-----------------+

                             |  Prepare Test   |

                             +-------+---------+

                                     |

                                     |

                             +-------v---------+

                             |  Execute Test   |

                             +-------+---------+

                                     |

                                     |

                             +-------v----------+

                             |  Generate Report |

                             +-------+----------+

                                     |

                                     |

                             +-------v----------+

                             |  Analyze Result  |

                             +------------------+

    Prepare Test

    在准备Test阶段, 最重要的是产生一份所有要执行的testcase的清单,在这里是TestCaseList.txt。显然, 可以通过扫描Test Case Repository目录来达成。 这是通过脚本 GenTestCaseList.bat来完成的。

    另外, 被测试产品的版本号是一个非常重要的信息, 必须在product.version文件里指定。

    Execute Test

    通过调用Batchrun.bat,所有定义在TestCaseList.txt里的testcase都会被顺序执行。执行过程中的所有log会记录到Run.log文件里。

    执行结果所有信息会在一个目录里保存, 如下图所示。

    其中,Batchrun.bat会把每一个Testcase的执行结果和错误信息(如果有的话)保存在TestResult.txt。这个文件会在"Generate Report"阶段被转换成TestResult.html

    Execute Test Twice

    因为GUI测试天然的不稳定性, 有时候, 你会希望执行两次,并且取两次运行合并后的结果作为最终结果。合并的算法是, 一个testcase,只要有一次通过, 就认为是通过的。 这钟方式能够有效的消除 不稳定性导致的testcase失败的机会。

    对于这种方式, 执行过程是下面这样.

    1.   Batch run all test cases in TestCaseList.txt)

    2.   Restore test environment

    3.   Batch run only failed test cass

    4.   Merge test results

    BatchRun_Rerun.bat能自动的完成上述的流程。测试结果目录是下面的样子。

    注意这里有三个TestResult文件。 TestResult.txt, TestResult_Rerun.txt TestResult_Merged.txt,分别是“第一次”,“第二次”,“合并后”的结果。

    Generate Report

    在所有testcase执行完之后, 调用GenReport.bat 会为该次执行产生一个HTML report,如下所示。这个动作已经集成在BatchRun.batBatchRun_Rerun.bat脚本里面。 不过也可以在测试执行完成后的任何时候调用GenReport.bat

     

    这个Report由总到分, 逐级展示测试结果。最上面是整个testrun的结果, 下面是TestModuleTestSuite的结果, 最下面是每一个testcase的测试结果。

    在查看每一条Testcase结果记录的地方, 你可以立即获得错误信息和当时的Screenshot 他们都是对于结果分析非常有用的信息。 实践表明, 这种便利性可以大大加快结果分析的效率。 如果你需要在Result Viewer里面查看更详细的信息, 那么可以打开ResultXML,这个文件正是Result Viewer需要的文件。

    Analyze Result

    为了方便结果分析, 需要用到GenExcel.bat这个脚本。 它可以根据测试结果文件(TestResult.txt)文件, 产生一个TestResult.xls文件。 利用Excel的能力, 你可以方便的根据测试结果过滤所有testcase 并且可以随时记录下分析结果。

    如果需要汇报测试执行结果, 那么可以在邮件正文给出汇总的信息,并附上TestResult.htmlTestResult.xls两个文件。 这样你的邮件基本上包含了各种stakeholder(team leader, manager, developer...)可能感兴趣的信息, 而且还不会显得凌乱。

  • Different QTP: 总结2:适应变化和可测试性

    2012-09-08 22:14:53

    总结2:适应变化和可测试性

    适应变化而不是控制变化

    相对于单元测试, 接口测试等自动化测试, 对基于GUI的测试面对一个很大的问题是GUI是容易改变的。

    对于这种情况, QTP给出了SmartIdentification 试图让工具更智能, 实践表明大多数时候它只是在帮倒忙。

    有些公司出台了一些Policy 限制DeveloperGUI元素识别标识符(比如ID)的修改。比如我现在的公司就提出过修改GUI的一些policy 结果是不了了之。因为很多Developer根本就记不住那些约束, 或者不愿意被约束。另外, 有些时候, 一些GUI元素是由工具生成的, ID变化不在Developer的控制之中。

    所以, 在设计GUI元素库的时候, 如何应对GUI的变化是需要重点考虑的。 我的答案是, 不去试图控制变化, 而是提高自己适应变化的能力。这就是“所见即所得”识别方式背后的动因。相对于“对象库”方式, “所见即所得”不仅仅带来了快速定位GUI元素的能力, 它还让修改变得很简单。当GUI元素修改了, 如果你只需要花几分钟就能修改好的你的代码, 那么你也不会太在意这种修改。

    可测试性与代码重用率

    可测试性与代码重用率看似两个不相干的东西, 但我发现代码重用率高的软件相对有更好的可测试性。 简单来说高质量的软件相对具有更好的可测试性, 及时developer在开发的时候并没有专门去思考“可测试性”这回事。

    有一本叫“重构”(refactory)的书说, “重复的代码”是代码臭味的一种。当代码具有臭味时, 你就需要去重构它。通过重构去掉“重复的代码”其实就是在提高代码的重用率。 代码的重用率一定程度上也能反映出代码的整体质量。 简单来说, 通过copy-paste构建的程序肯定比通过抽象构建的程序代码质量差, 因为它导致大量的重复代码。

    为什么 代码重用率高的程序一定程度上也是可测试性高的程序? GUI元素库对于元素定位的每一种基本方法(包括组合元素定位)是针对某一类型的GUI元素的抽象。一个程序, 如果它的GUI部分的代码重用率高,那么就有更多的GUI元素其定位标识是有规律的。 那么同样数量的定位方法就能覆盖更多的GUI元素。 一个程序如果是通过copy-paste建立起来的, 因为paste之后, 程序员是可以任意修改代码的, 这种修改可能破坏了原有的GUI元素定位规律。所以结果就是, 不得不写更多GUI元素定位基本方法(或者组合元素)来覆盖所有的情况。

    所以, 我发现, 使用这个GUI元素库, 越是高质量的软件,其测试套件开发起来越轻松。

  • Different QTP: 总入口

    2012-08-28 14:59:24

  • Different QTP: 业务逻辑封装接口设计

    2012-08-28 14:56:26

    业务逻辑封装接口设计

     

    为了达到“testcase scripttestcase的描述有简单而直接的对应”这样一个目标, 需要对产品功能有一个全面而系统的封装, 然后提供一套APItestcase script调用。 显然, 这会导致大量的API单元产生出来。

    QTP提供了resuable action作为业务逻辑封装的工具, 但是resuable action过于重型, 而且并没有提供与其重量级相对应的强大功能。  function相比于reusable action提供了同样的封装能力, 但体积只有1/10,而且使用方式更自然。  这也是不用reuable action的若干重要原因之一。

    另外一个必须要考虑的问题就是如何降低这套API使用的难度。 这里有一些技巧。

    命名规则

    VBS没有Module的概念, 但显然数量众多业务逻辑封装函数必须组织在模块里。 所以我们在命名规则上来模块化。一个业务封装函数的命名如下

     BIZ_ModuleName_DoWhat(paramters)

    首先BIZ前缀把它与framework里的其他函数分离出来。 ModuleName是第二个前缀, 指出了模块名。 第三个参数才描述了函数所封装的业务。

    文档化

    VBS没有专门的文档化定义(类似JavaDoc的东东)。当然你不能对它责怪太多, 毕竟这是上个世纪70年代的语言。 我使用Doxygen加上一个定制的filter来生成framwork里所有函数的文档。

    比如下面这段的代码

    ' create account with given parameters.
    '
    ' @param resourceType Hierarchy/Group/Device
    ' @param param1 the param to set value in page 1
    ' @param param2 the param to set value in page 2
    '
    ' @see GUI_Field_HierarchySelect_Set
    ' @see GUI_Field_GroupSelect_Set
    ' @see GUI_Field_ProfileSelect_Set
    Function BIZ_Account_Create(accountName, roleName, resourceType, param1, param2)

     

    通过Doxygen生成的文档像下面的样子


    去除页面依赖

    这里的“页面依赖“是一种函数前置条件。 它特指业务逻辑封装函数对进入此函数时的当前页面的要求。比如一个BIZ_User_Create函数, 它的前置条件可以是“当前页面在User Management页面”。 不满足前置条件时, 调用一个函数通常会导致object not found错误。

    但是如果BIZ_User_Create函数针真的对前置条件如此要求, 那么成百上千的类似的业务封装函数会让Testcase script的设计者“步步惊心”。 为了消除这种不必要的心理负担。 我对业务逻辑封装函数的要求就是:只要有可能, 所有业务逻辑函数都要去除页面依赖。也就是说无论当前应用程序处于那一个页面, 函数都能正常工作。对于大部分应用程序来说,前置条件的要求其实是“用户已经登陆进入系统, 但可以处于任意页面”。

    显然, 这会导致更多的执行时间。在函数内部, 首先做的就是导航(通过菜单或别的方式)到实际开始业务逻辑的页面。 但是相对于其带来的接口的简化, 和后续的维护简化。 这点成本简直就是微不足道。

    这是一个很小的规则, 但它在提高API的可用性方面效果非常大。
  • Different QTP: 业务逻辑封装原则

    2012-08-28 14:53:30

    业务逻辑封装原则

    业务逻辑封装原则就是 严格分离测试逻辑和业务逻辑

    这里的测试逻辑就是指Testcase里面的逻辑, 业务逻辑就是产品功能相关的逻辑。

    这个原则看上去很简单明了, 但是实践时非常容易把二者混淆。

    为了“严格分离测试逻辑和业务逻辑”, 我对开发人员的角色进行了划分, 分为“framwork开发人员”和“testcase script开发人员”。 framework开发人员在开发封装业务逻辑的API的时候, 心里要忘掉任何具体的某个testcase的测试逻辑, 而只关心如何能从产品业务逻辑的角度提供最合适的API

    其次, 我还对函数所在的位置进行了隔离。 所有业务逻辑封装的代码放在函数库目录里, 而与测试逻辑相关的一些共享函数放在TestCase repository目录里。

    即便如此, 开发人员也会遇到“不能确定一个function是属于业务逻辑还是测试逻辑的问题”。 我举了两个个例子。

    ·         验证用户存在

    验证用户存在是一个经常会用到的功能, 所以对于开发人员有极大的诱惑把它封装为一个函数, 放在framework里。 但实际上这是错误的, 因为“验证”这个动作本身就与业务逻辑无关, 而属于测试逻辑。

    ·         创建一组特定的 “角色,用户, 用户组”

    在某件些testcase的前置条件里, 要求一组特定的“角色,用户,用户组”存在。 写一个函数封装这一系列操作是一个好主意, 但这个函数仍然属于测试逻辑。 因为对于产品来说, 并没有一组“角色,用户, 用户组”这个概念。

  • Different QTP: 总结

    2012-08-26 22:22:30

    总结:关于framwork设计的一些思考

    到这里, 我已经把这个framework里最重要的部分, 开发testcase的基石, Function Library介绍完了。

    在设计这个Function Library的时候, 有两个最重要的抽象层, 一个是对产品业务逻辑的封装, 一个是对GUI元素访问的封装。 对于业务逻辑封装, 最重要的原则是”把业务逻辑和测试逻辑严格分离“, 另外就是不要使用QTP提供的Reusable Action 对于GUI元素的封装, 我舍弃了QTP本身提供的对象库, 而完全用描述性编程的方式建立了一套”所见即所得“的GUI对象查询库。

    为什么是这样子抽象?是两层抽象, 而不是一层(仅仅提供一些GUI元素操作的封装), 或者三层(提供data-driven/keywork-driven类似的用户接口)? 我的经验告诉我, QTP软件测试项目最大的挑战其实是可维护性。无论你是用record还是层层封装, 基本上你都能实现一个testcase 但是真正的问题在于当testcase的总数上升到成百上千的数量级,代码库是否还能保持清晰? Testcase是否能够适应今后产品的变化?维护人员是否能快速定位错误?当前的抽象模型是我认为对我目前项目最合适的。

    我始终认为,好的framwork设计者应该尽可能取悦framwork的用户。 一个QTP test framwork的用户其实有三类,

    ·         业务逻辑封装函数的开发人员

    ·         testcase script开发人员

    ·         testsuite 维护人员

    一个framwork的设计者,最好同时也从事上面提到的三种类型的开发 ,这样才能”叫好又叫座“

    最后, 是没有最好的framework 而只有最合适的framework 如前言说讲, 这是一种“非 常规”的开发模式,特别是 舍弃了很多QTP提供的智能工具。QTP提供这些工具的目的是为了支持它的”keyword driven”这样一种开发模式。客观的说, 这是一种易用,快速的开发模式,但个人之见是仅仅适合小型,且短期的项目, 而且和普通的编程语言开发模式非常不一样(java/c#/c++)。基本上, 我是把QTP测试的开发模式, 转变成了普通语言开发的模式。

    我曾经一位在HP工作过的同事告诉我, HP内部也是用QTP推荐的模式, 而且其项目也是不小。 所以我有时候怀疑这是不是因为我使用Java太长以至于形成了思维定势, 也许QTP本身的模式并不是那么脆弱, 低效。 但是无论怎么样, 我现在使用 的模式也有成功案例的支持, 所以我想可能这个也是一个因人而异的事情吧。 只是希望一些新的想法, 方法的出现能够激发更多人的灵感, QTP测试项目开发模式更加完善, 进一步提高QTP测试开发人员的生产效率。

  • Different QTP: GUI元素库:用Screen-Field解决复杂组合元素

    2012-08-26 22:17:59

    GUI元素库:用Screen-Field解决复杂组合元素

    有一些Client-Server模式的企业应用程序, GUI输入元素是非常复杂的, 不但一个Form里包含的输入元素多, 而且有很多组合类型。 比如下面的例子。(TODOresource select window

    对于这种情况, 使用前面描述的CombinedElement方案并不合适。 从设计的角度, 这里有一个“代码复杂度”和“用户接口的简洁”的平衡问题。原因是CombinedElementElementQuery FormInput是耦合的,是你中有我, 我中有你的状态。 如果是小型的, 少量的且重复使用率高的CombinedElement 那么是合适的。 因为通过添加少量的代码就能把Combined Element集成到一个统一的框架里, 提供给调用者一个统一的接口。 但是要集成大量且复杂的Combined Element 那么结果就会是把GUI元素库代码搞得一团糟。

    所以对于这种情况, 还是在GUI Element库的外面开发一个专门的框架比较好。这就是将要介绍的Screen-Field方案。

    首先看一下定义。

    Field

    Field是一个逻辑上的Input单元。 它可以是基本Input元素, 也可以是自定义的组合元素。

    一个Field可以被一个且仅有一个标识符定位( 同基本Input元素一样)。

    一个Field可以被设置值。 对于基本Input元素,框架会调用GUI_GetElement来定位该元素。 对于组合元素, 每一个Field类型有一个对应的设置值的方法, 如下

    '@param objContainer  a window that the field resides

    '@param strMark       a string to identify a specific field    

    '@param strVal        the value to be set

    GUI_Field_FieldName_Set(objContainer, strMark, strVal)

     

    Screen

    Screen简单来说就是Field的集合。

    每一个Screen都有一个名字, 还有一个对应的Screen配置文件, "screen"作为后缀名, 比如Login.screen Screen配置文件里定义了Field的类型。 比如

    #in Login.screen

    Login name=Edit

    Password=Edit

    %daylight savings%=On

    Time zone=List

     

    Screen支持SmartInput 所以基本Elemen无需自定类型。碰巧上面的Login.screen里所有元素都是基本元素, 也就是说里面可以是空的。 但这个文件还是必须要的。

               +-----------+           +------------+
               | Screen    | 1        1| xxx.screen |
               |           +---------->|------------|
               +-----+-----+           | field=type |
                    1|                 |            |
                     |                 |            |
                     |                 |            |
                    n|                 +------------+
               +-----v-----+
               |   Field   |
               +-----------+

    Screen的接口函数如下
    GUI_Screen_Set(objContainer, screenName, strValueMap)

    Screen-Field library GUI element library的关系
        |-----------------------|
        |Screen-Field library   |
        |-----------------------|
                    |
                    | 
                    |
          |---------V---------|
          |GUI Element Library|
          |-------------------|

     可见 Screen-Field library依赖于, 但不侵入GUI element library 所以无论有多少复杂的Field 都不会丝毫影响到GUI element library


  • Different QTP: GUI元素库:快捷方式

    2012-08-26 22:15:50

    GUI元素库:快捷方式

    所谓的快捷方式, 其实是GUI元素库的用户接口。 到目前为止, GUI元素库主要功能就有了。 但还差一步, 就是提供一个简单高效的接口给调用者。这个专门的用户接口集合将会更进一步增强调用者“所见即所得”的感觉。

    缺省主窗口

    大多数情况下, 一个应用程序都只有一个主窗口。 其他部件要么是在主窗口里面, 要么是主窗口的子窗口, 比如对话框。既然如此, 那么container对象就不需要在每次函数调用时指定了,而只需在测试程序初始化的时候 设置, 或者在主窗口切换时指定。

    GUI_SetMainWindow(objContainer)

    针对每一个元素类型提供一个接口

    对于所有的基本元素和组合元素, 都提供一个查询的快捷方式。另外对于Find方法和Get方法要分别提供一套API,下面函数名F前缀表示Find, G前缀表示Get对于Web Application 有下面的接口函数。

    FLink(strMark)

    FEdit(strMark)

    FList(strMark)

    FCheckBoxButton(strMark)

    FRadioButton(strMark)

    FButton(strMark)

    FImag(strMark)

    FTable(strMark)

    FDateTime(strMark) 'Combined input

    ... ...

    GLink(strMark)

    GEdit(strMark)

    GList(strMark)

    GCheckBoxButton(strMark)

    GRadioButton(strMark)

    GButton(strMark)

    GImag(strMark)

    GTable(strMark)

    GDateTime(strMark) 'Combined input

    ... ...

    FormInput(strInputMap, strTypeMap)

    在这样一套API的支持下, QTP代码编写可以成为一件很轻松而快乐的事情:),请看下面登陆的例子。

    登陆

    GEdit("Login name").Set "ABC"

    GEdit("Password").Set "111111"

    GCheckBox("%daylight%").Set "ON"

    GList("Time Zone").Select "GMT+12, Marshall Islands Time, Fiji Time"

    GButton("Login").Click

     

    登陆的FormInput

    FormInput("Login name=ABC; Password=111111; %daylight%=ON; Time Zone= GMT+12\, Marshall Islands Time\, Fiji Time", "")


     

  • Different QTP: GUI元素库:组合元素

    2012-08-26 22:10:58

    GUI元素库:组合元素

    前面介绍的都是QTP提供的基本元素的查找和操作。 对于一个软件产品,经常会看到这样一种情况,  页面上的几个基本输入元素在逻辑上是一个输入元素。比如下面的例子。

    三个基本输入元素, 一个WebEdit 两个WebList构成了一个逻辑上的“时间输入控件”。 这种控件, 我叫他“组合元素”。

    对于这些组合元素, framework里不做特殊处理是可以的, 前提是每一个基本元素都能方便的识别出来。但实际往往并不如此乐观, 比如页面开发人员为了使得组合元素更像一个整体, 他们结合得都比较紧密, 以至于没有一个“可见的标识”来标识其中的某一个基本元素, 比如上面例子中的Minute输入list

    Class来封装

    这种情况, 就需要针对组合元素来做专门的封装。 在封装组合元素时使用到了VBS里的Class VBS对面向对象支持是相当之弱, 这也是framework里仅有的使用Class的地方。 对于封装组合元素的Class 有下面的接口定义。以上图为例。

    Class DateTimeInput

         Function Init(objContainer, strMark)

         Function Exist(nSec)

         Function SetValue(strVal)

         Function Value()

    End Class

     

    ' CombinedInput的工厂函数, 负责创建CombinateInput实例

    Function GUI_CombinedInput_Create(xtype, objContainer, strMark)

     

    对于DateTimeInput组合元素, starMark是第一个EditLabel 也就是“Begin-Date”或者“End-Date”。 基本算法是, 找到第一个WebEdit 然后向上找到TR 然后在到TR的第二个TD里的两个WebList就分别是HourMinute

    CombinedInputGUIElementQuery的集成

    一个很自然的想法是把CombinedInputGUIElementQuery结合起来。 在实践中, 我也是这样做的。 但是更进一步, CombinedInput集成进SmartInput是不可以的。因为这会把SmartInput弄糊涂, 它没法判断一个基本Element到底是独立的一个还是属于某一个CombinedInput 那也就是说FormInput里对CombinedInput是必须指定类型的。

  • Different QTP: GUI元素库:复杂对象描述机制

    2012-08-26 22:06:31

    GUI元素库:复杂对象描述机制

    在介绍FormInput的时候,在传递Form里要设置的数据时, 使用了下面一个字符串。

    "Activated=True; Name=xs; Schedule=Monthly; Day=31; Hour=2; Minute=32;Day#2=31; Hour#2=2; Minute#2=32"

    直觉告诉你这代表了一个Map 没错,这里用到了framework里相当重要的一个基础技术, 那就是复杂对象的描述技术。

    对于QTP测试项目来说, 传递复杂的数据结构是经常会碰到的情况。但是无论VBS还是QTP都没有提供一种方便的解决方案。

    本质上, 我们需要一个像JSON一样, 用字符串描述复杂数据结构的技术。 借鉴JSON 我定义了下面的语法规则。它甚至JSON更简单, 因为它只有一种数据类型“String”。  

    map :
        {}
        {members}
    members :
        pair
        pair; members 
    pair :
        NULL # NULL means nothing. NULL will be ignored, e.g "a=1;;b=2"
        string = value #string will be trimmed
    array :
        []
        [elements]
    elements:
        NULL # NULL means nothing. NULL will be ignored, e.g "abc,,def"
        value
        value, elements
    value :
         NULL # "" 
         string
         map
         ary
    string :
        char chars
    char
        normal # any char but not \ [ ] { } ; , =
        \[
        \]
        \{
        \}
        \=
        \;
        \,
        \\
        \s # (32)
        \t # (9) 
        \n # (10)
        \r # (13)

    使用这个语法, 能描述MapArray 和他们的任意的组合。例子如下

    ·         map的例子是 "{name=Jack; age=30}"

    ·         array的例子是 "[Jack, Tom]"

    ·         组合的例子是 [{name=Jack; age=30}, {name=Tom; age=20}]

    ·         特殊字符用'\'来转义, 例子是 {name=Jack\, Tom; age=30}

     

    复杂对象描述机制极大的提高了代码生产率, 并且让你的代码看上去很美。 如果没有它, 我一定会不去做任何QTP的项目,因为我忍受不了如此丑陋的代码。 想象一下用Dictionary逐个赋值的方式来构造输入数据。。。

  • Different QTP: GUI元素库:智能查找与批量输入

    2012-08-26 22:01:34

    GUI元素库:智能查找与批量输入

    SmartInput

    SmartInput提供了无需指定元素类型就能查找Input元素的能力。 还是以前面的login页面作为例子。

    如果要得到Login name 对应的edit box 代码如下

    Set bj = GUI_Html_GetElement(objContainer, "Login name", "WebEdit")

    如果使用SmartInput 代码如下

    Set bj = GUI_SmartInput_Get(objContainer, "Login name")

    SmartInput会按照下面的顺序依次查找element。如果最终没有找到,那么返回Nothing

    "WebEdit", "WebList", "WebCheckBox", "WebRadioGroup", "WebFile"

    对于单独的一个Input 看上去意义不大。 但是如果你面对一个具有几十个InputForm,那么结合FormInput 在提高开发效率方面就有可观效果。

    FormInput

    FormInput提供批量读取/设置Forminput的值的方法。

    对于企业应用程序, 常常会有处理包含几十个InputForm的情况。如下例所示, 这个界面包含16Input控件, 而且这还只是一个Wizrd5个界面之一。

    基本形式

    对于这种情况, 批量输入数据是很有意义的。 比如设计下面一个接口。

    GUI_FormInput(objContainer, nameValueMap, nameTypeMap)

    调用代码如下

    GUI_FormInput(objWizard, _

         "Activated=True; Name=xs; Schedule=Monthly; Day=31; Hour=2; Minute=32;Day#2=31; Hour#2=2; Minute#2=32",

         "Activated=JavaCheckBox; Name=JavaEdit; Schedule=JavaList; Day=JavaSpin; Hour=JavaSpin; Minute=JavaSpin; Day#2=JavaSpin; Hour#2=JavaSpin; Minute#2=JavaSpin")

    相对于每一个Input控件逐个调用相应的查找函数, FormInput节省了的大量的代码。

    利用SmartInput

    GUI_FormInput(objWizard, _

         "Activated=True; Name=xs; Schedule=Monthly; Day=31; Hour=2; Minute=32;Day#2=31; Hour#2=2; Minute#2=32",

         "")

    可以看到,第三个参数为空字符串,也就是不用指定Input的类型。 因为所有的Input控件都被SmartInput支持。

    返回已有值

    另外, 调用GUI_FormInput函数的时候, 返回Input的值在某些情况下也是很有用的功能。实际上对于实现来说, 在设置Input的值时, 获取Input已有的值是很容易的。 所以FormInput还会返回Form里所有Input的值, 如下所示。

    Set mapFormVal = GUI_FormInput(objWizard, _

         "Activated=True; Name=xs; Schedule=Monthly; Day=31; Hour=2; Minute=32;Day#2=31; Hour#2=2; Minute#2=32",

         "")

  • Different QTP: GUI元素库:标识符匹配的规则

    2012-08-26 22:00:20

    GUI元素库:标识符匹配的规则

    GUI 元素查找函数库提供了一个入口函数,

     GUI_Html_FindElement(objContainer, strMark, xtypye)

    这个函数的参数strMark是一个字符串, 在查找时用来匹配元素的某一个属性。 说到“匹配”, 我们马上会想到“相等”, 这是最常用的一种匹配。如果为了提供更大的灵活性, 还应该支持“正则表达式”匹配。 实践表明, 在“相等”和“正则表达式”之间, 还有一种常用的匹配关系“包含”。”包含“关系匹配的好处是你可以只指定关键字, 而又不需要写稍微复杂的正则表达式。

    比如你想匹配一个页面抛出的错误信息.

    <div class="Error">Error: invalid user name. user name shouldn't contain special characters like "#/' ...</div>

    对你来说, 你其实只是想确定页面错误信息里有“invalid user name”就可以了, 那么这里就会用到“包含”关系的匹配。

    Set bjErrDiv = GUI_Html_FindElement(objContainer, "<DIV>%invalid username%", "WebElement")

    稍微解释一下strMark 因为这里xtype指定的是WebElement 对于这种类型, 底层查找函数是GUI_Html_FindByTextAndTag(objContainer, strText, strTag, xtype) 但是这里有一个问题是, 如何通过strMark一个字符串来表达strTextstrTag两个内容。 这里就设计了"<tagName>innerText"这样一种表达形式。

    为了支持以上三种匹配关系, 而且代码书写方式还要自然, 高效。 我定义了下面的规则

    ·         如果strMark是“/.../”的形式,那么表示用正则表达式匹配

    ·         如果strMark是“%...%“的形式, 那么表示用“包含”匹配

    ·         如果strMark不是上面两种形式之一, 那么表示用”相等“匹配

    通过匹配规则的扩展, 查找函数库的调用者获得了更大的查找对象的能力, 同时几乎没有增加额外的负担。

    额外说明

    有些查找方法不支持正则表达式匹配或者”包含“匹配, 比如从object  repository查找元素。 这些限制应该在接口文档里明确的指出。

  • Different QTP: GUI元素库:查找函数

    2012-08-26 21:51:24

    GUI元素库:查找函数

    QTP对于GUI对象查找的能力是相当之强的。 这个通过Object Repository里属性设置就可以看出来。 对于某一个特定的软件产品来说, 其实并不需要所有的这些能力。 设计GUI元素查找函数就是分析所有这个软件产品涉及到的GUI元素, 把他们分门别类, 针对每种类型设计相应的查找函数。

    GUI元素查找函数底层都是使用描述性编程(Descriptive Programming)来查找GUI元素的。

    我们通过一个实例来详细介绍这个GUI元素查找函数库。

    查找函数

    一个真实的对Web Application测试的QTP project,有下面一些查找函数

    ·         GUI_Html_FindByTextAndTag(objContainer, strText, strTag, xtype)

    这个函数具有根据“inner text”和“tag”来查找对象的能力

    ·         GUI_Html_FindById(objContainer, strId, xtype)

    这个函数具有根据ID属性来查找对象的能力

    ·         GUI_Html_FindByName(objContainer, strName, xtype)

    这个函数具有根据Name属性来查找对象的能力

    ·         GUI_Html_FindFromObjRepo(objContainer, strName, xtype

    这个函数具有从Object Repository查找对象的能力。 存在极少的一些element 把它放到Object Repository里可能是更有效率的方式。

    ·         GUI_Html_FindByLabel(objContainer, label, xtype)

    这个函数具有根据Label来查找Input元素的能力。 关于什么是label 请参考web方面的文档。 前面例子的GEdit("Login Name") 就是根据Label来查找Input元素的实例。

    ·         GUI_Html_FindTableByColumns(objContainer, strColNames)

    这个函数具有根据column name来查找Table的能力。 实践表明,Table是一个经常会用到的GUI Element Column name是最能标识一个Table的(所见即所得风格)。

    入口函数

    作为一个封装良好的Library 并不需要把这么多接口统统暴露出来。 实际上, 这里还提供了一个总入口作为对外的接口。

    GUI_Html_FindElement(objContainer, strMark, xtypye)

    这个函数会根据xtype之不同, 按照特定顺序调用上面的函数来查找一个GUI element,直到找到为止, 或者返回Nothing表示没有找到。

    作为设计者, 你必须要明确每一种Element可能具有的查找方式, 比如

    linkidnametextandtag objrepo

    webeditidnamelabelobjrepo

    webtableid, columns, objrepo

    总的来说。 外部程序只需要调用查找函数库的入口函数就可以完成绝大多数GUI element的查找。

    FindGet

    如果你使用Object Repository来定位GUI 元素,QTP提供了一个很好的机制来提高代码的稳定性, 它就是一个等待对象出现的机制。 它的原理是这样,比如这样一行代码:

    Browser("XXX").Page("YYY").WebEdit("loginform.:name").Set "ABC"

    如果执行到这里的时候,WebEdit("loginform.:name")还未出现(比如page还没有完全load), 那么QTP会等待一段时间。 只要在这个时间范围内, 此元素在页面生成, 就会顺利执行下去, 而没有任何类似对象不存在的错误抛出。

    但是对于GUI对象查找函数, 这种机制是无效的, 因为它使用Descriptive Programming 所以有必要提供类似机制。 GUI元素查找库里提供了下面一个接口:

    GUI_GetElement(objContainer, strMark, xtypye)

    GUI_GetElement相对于与GUI_FindElement之不同在于, 前者有一个timeout时间, 如果在timeout时间之内没有找到GUI element 会抛出一个Error;而后者是马上返回, 如果没有找到, 返回Nothing GUI_GetElement的实现像下面的样子

    GUI_GETELEMENT_TIMEOUT=20 'seconds

    Function GUI_GetElement(objContainer, strMark, xtype)

         Set ele = Nothing

         for idx=0 to GUI_GETELEMEN_TIMEOUT

               Set ele = GUI_FindElement(objContainer, strMark, xtype)

               if Not ele Is Nothing Then

                    Exit For

               end if

               Wait 1 'sleep 1 second

         next

         AssertNotNothing ele, "Failed to find object with mark<"&strMark&">, xtype<"&xtype&">"

    End Function

    Index标识

    有时候在一个界面上, 两个Element具有相同的标识。 比如下面的例子, 开始时间和结束时间都具有Day HourMinute三个控件。

    对于这种情况,就需要指定Input控件的序号, 也就是所谓的“Index”。 默认index1 无需指定。 如果index不是1,比如2 那么用“#2“来标识。比如得到第二个“Day”控件。

    Set bjEndDay = GUI_FindElement(objContainer, "Day#2", "JavaSpin")

    使用Index标识带来一个问题, 就是“#”成了特殊字符。 如果你的标识符里碰巧有# 而且碰巧#后面是数字, 那么需要用\来转义。 其他情况都不需要转义,程序会把#解释为字面量而不是Index标识符。


  • Different QTP: GUI元素库:架构

    2012-08-26 21:46:36

    GUI元素库:架构

    GUI元素库的总体结构如下图所示。

        |----------------------------------------| 
    
        |            Shortcut                    | 
    |----------------------------------------|
    |FormInput | SmartInput | CombinedElement|
    |----------------------------------------|
    | GUIElementQuery |
    |----------------------------------------|

     

    其中,

    ·         GUIElementQuery

    这是一些提供最底层的GUI元素查找函数。比如针对ID进行查找, 针对“Attached Text 进行查找。 

    ·         CombinedElement

    对组合元素的封装。 一个软件产品, 总会有一些组合的输入控件, 比如“日期”输入控件可能由“年月日”三个Edit Box组合而成。

    ·         SmartInput

    SmartInput提供智能查找支持。也就是在查找GUI元素时, 不需要指定其类型。 SmartInput会按照给定的类型顺序, 逐个尝试。 SmartInputFormInput的基础。

    ·         FormInput

    FormInput提供对一个Form里的多个Input元素进行批量的读取或者设置值的操作。 FormInput把测试数据和测试逻辑分离开来, 提高代码开发的效率。 而且 建立在SmartInput之上的FormInput对于大多数基本GUI元素无须指定类型, 这样可以仅仅把“名字:值”传递给SmartInput就可以对一个Form进行设置值的操作, 进一步减轻开发的负担, 提高开发效率。

    ·         Shortcut

    Shortcut封装了底层最常用的方法, 并提供一套快捷访问接口。 比如前面例子中GEdit("Login Name")就是一个快捷方式。 它实际上会调用到下面的代码。

    GUI_GetElementByLabel(Browser("XXX").Page("YYYY"), "Login Name", "WebEdit")

    可见, GUI元素库不但提供了取代Object Repository的功能, 还能进行批量数据输入, 而且通过其shortcut接口, 还提供了更高效的代码书写风格。

  • Different QTP:两个抽象之二:GUI元素操作抽象层

    2012-08-26 21:31:06

    两个抽象之二:GUI元素操作抽象层

    第二个抽象层是关于对GUI元素的操作的封装。

    在我的项目里,除了最顶层的Window  几乎完全不使用Object Repository来定义GUI元素。而是使用一套对GUI元素封装的API

    对于GUI元素的封装的API 它的目标是, 在编码的时候, GUI元素的操作是“所见即所得”。 什么叫做“所见即所得”, 它的意思是, 当访问一个对象时, 通过你能看见的标识就能得到这个对象。

    这个的关键是“你能看见的标识”。 如果你用过QTPrecord 你得到代码里,对于GUI对象的访问通常都是通过“看不见的标识”, 比如HTML ID index等等。

    例子

    比如下面的一个login web页面。

    对于login操作, 如果通过record,代码像下面的样子

    Browser("XXX").Page("YYY").WebEdit("loginform.:name").Set "ABC"

    Browser("XXX").Page("YYY").WebEdit("loginform.:pswd").Set "111111"

    Browser("XXX").Page("YYY").WebButton("loginform.:login").Click

    而使用“所见即所得”的GUI元素库, 代码像下面的样子

    GEdit("Login name").Set "ABC"

    GEdit("Password").Set "111111"

    GButton("Login").Click

     

    如你所见, 对元素的引用通过你能看见的标识,而不是id或者name你不能看见的标识。这也是不使用Object Repository的重要原因之一。

    优点与缺点

    这样一种抽象/封装方式带来的好处,首先是开发效率的提高。 在写代码的时候, 完全没有了Object Repository的束缚, 不用再在editorobject repository manager之间来回切换,坦白说, 那种开发方式几乎让我疯掉。 你所做的就像在编写任何其他语言代码一样,以一种最舒服的方式在开发你的function或者testcase script

    另外是在对代码修改以适应产品GUI的变化时, 你仍旧不需要打开inspectorobject repository manager 去重新识别元素。 你是需要把你看到的变化在代码里重新匹配即可。

    缺点。 对某一个特定的产品, 建立一套完整的GUI对象查找机制并不是一个很小的工作量。 而且在这个过程中还会经常遇到“非常规”需要特别处理的GUI元素。 为了达到易用性和能力的平衡, 需要做出很多设计上的取舍, 这需要一个资深的程序设计人员才能完成。 如果非要量化的话, 我认为其设计者需要三年通用的面向对象语言开发经验(Java/C#/C++)。

    GUI元素库是framwork里最重要的部分, 也是工作量最大的部分。 它也是业务逻辑库的基础。 所以我会用很多笔墨来详细介绍它是设计的, 和设计过程中面对的各种问题和解决方法。
  • Different QTP: 两个抽象之一:产品业务逻辑抽象

    2012-08-26 21:19:28

    两个抽象之一:产品业务逻辑抽象

     第一个抽象层是关于对产品业务逻辑的封装。 它的目标是,在设计testcase script时, 要求做到“testcase scripttestcase的描述有简单而直接的对应”。 也就是说在阅读一个testcase script的时候, 加上少量的注释, 就像在阅读这个testcase的人类语言描述。

    例子

    我们来看一个testcase的描述。 它关注于对测试逻辑的描述, 它的语句表达了业务层面上有意义的步骤, 不会/很少 涉及到很具体的GUI的操作, 比如下面的例子。 这是一个测试创建新用户的testcase描述。

    Preconditions:

    Login as Administrator

    Steps:

    1.  goto admin->user management

    2.  create a new user with

    1)  name: ABC

    2)  password: 123

    3.  logout administrator

    4.  login as ABC with initial password 123 and change password to 234

    5.  Logout ABC

    6.  Login as ABC and with password 234

    Checkpoints

    ·         step2, ABC is created

    ·         step6, ABC can login

     

    一个testcasescript 应该像下面的样子

    'precondition

    BIZ_LoginAsAdmin

     

    'create user ABC

    BIZ_CreateUser "name=ABC;password=123"

    AssertTrue BIZ_UserExist "ABC", "user ABC not created"

    BIZ_Logout

     

    'login as ABC and change pswd

    BIZ_LoginWithInitPswd "ABC", "123", "234"

    BIZ_Logout

     

    'login as ABC again

    loginFlag = BIZ_Login "ABC", "234"

    AssertTrue loginFlag, "ABC failed to login with pswd 234"

     

    如你所见, 一个testcase script的开发人与, 在编写testcase script的时候, 同样也只关注测试逻辑, 他用framework提供的API来把人类语言的测试逻辑“翻译”为VBS语言。 这就是第一个抽象/封装所要达到的目标。

    优点与缺点

    这种方法的最大优点就是。 testcase script的开发效率更高, 同时维护成本更低。

    首先是项目代码更能适应变化了, 因为他对两个变化源彻底的分离, 无论是产品功能的变化, 或者测试逻辑的变化, 其影响范围都限制在了最小。

    testcase script的简单易读使得后期维护更容易。 设想你去维护一个别人开发的QTP project 当你打开一个失败的testcase时, 你能够通过读代码就能明了其测试逻辑, 马上开始调试工作。。。

    Testcas script的开发效率更高。在对产品功能有一个全面而系统的封装的天体下, Testcase开发的要求降低了,无论是复杂度还是代码量都会少很多。  但是, 这需要更多的时间来开发framwork 所以总时间并不一定会少。 但我能确定后期维护时间会少很多。

    这样做的缺点也有, 就是对framework开发人员要求更高,除了会使用QTP 必须具有良好分析和抽象的能力, 我认为至少要有一年的通用的面向对象语言开发经验(Java/C#/C++)。 公司需要更高的价格才能在市场上雇佣到这样的人。

    在实践的过程中,我发现对于framework的开发者的要求,首先要非常熟悉业务, 第二就是 具有良好的分析和抽象的能力。即使你满足了这两个要求, 还需要时刻把下面一个原则记在心里。

  • Different QTP: 前言

    2012-08-26 21:02:24

    这是我对两年来QTP工作的一份总结。详细介绍了Framework的设计理念和方法。

    前言:Different QTP

    为什么叫做“Different QTP”, 具体来说, 在我的项目里, 下面一些是不使用的

    1.    Object Repository(尽量不用)

    2.    Reusable Action(严禁使用)

    3.    Smart identification(严禁使用)

    4.    Keyword view(没有必要用)

    5.    Reporter ReportEvent(尽量不用)

    简单来说, QTP里一些提高易用性的工具, 基本上统统都用不上。

    QTP(QuickTestPro)作为最流行的商业软件测试工具,像其他商业软件一样,在软件的易用性方面都是费尽心机。 设计了很多工具来让用户能够快速开始设计一个测试脚本。 但是, 最近我在对这两年QTP测试工作进行总结时, 发现在我的项目里, 并没有使用QTP提供的这些提高“易用性”的工具, 甚至很多工具是严禁使用的。 可以说, 我做QTP测试项目的开发方式和QTP的推荐方式完全不一样。 这就是我把文章命名为“Different QTP“的原因。

    在我看来, QTP提供的很多工具只是为了Quick 比如record 对于小项目尚能接受, 但对于比较大且长期运行的项目, 却带来了维护的问题, 反而是效率低下的。

    也许你觉得这样比较“激进”, 但我的实践证明了这些措施是有效的。 在两年的时间里,我按照这样的理念设计了QTP测试的framework 在此framework基础上 有超过1000testcase也是在使用同样的理念开发出来。整个Team都觉得这些testcase的开发效率和可维护性远远超过了按照常规的QTP方式开发的testcase。这也是我有信心把这个过程中的经验教训共享出来的原因。

    一个QTP测试项目如果简单的划分, 包括三个部分。 如下所示。

           +-------------------------+    +--------+
           |                         |    |        |
           |      TestCases          |    |        |
           |                         |    | Mngt.  |
           |                         |    | Tools  |
           |                         |    |        |
           +------------------------->    |        |
           |                         |    |        |
           |                         |    |        |
           |                         |    |        |
           |   Function Library      |    |        |
           |                         |    |        |
           |                         |    |        |
           +-------------------------+    +--------+

    Testcase就是根据手动测试的Testcase实现的QTP testcase

     

    Mngt. Tools是一些脚本工具, 来自动化批量执行, 产生测试报告, 生产代码文档等工作。

    Function Library是对产品操作的封装库。 对产品的操作既包括高层次的业务层面的操作比如“创建一个用户”, 也包括低层次的对某个GUI元素的操作, 比如“Click Button”。它的封装形式并不限于Function QTP支持的Reusable  Action也是一种封装形式。一个测试框架 应该包括Function LibraryMngt Tools两个部分,但因为Function Library是主要的部分, 所以, 有时候也把Function Library叫做Framework。在本文里, 并不涉及Mngt. Tools, 所以两个名词是可以互换的。

    决定一个framework最否有效解决问题的关键是它对问题领域的抽象。 所以下一篇, 我要介绍Framework里最重要的两个抽象。

     1:通常来说, 一个framework几乎都是产品特定的。 不可能完全把测试A产品的framework用在B产品上。 但是framwork的设计思路是可以共享的。 下面的系列文章里我会具体介绍这些设计思路, 希望能对你能有所启发, 能引起你的一些思考。 任何你的idea也欢迎和我讨论。
Open Toolbar