对于做web端自动化测试的人来说,可能接触selenium比QTP还要多,但是我们在做基于selenium的二次开发的时候,经常会说到二次开发是为了易于维护,很多人可能不懂得维护的价值是什么,和到底要维护什么。今天专门写一篇关于二次开发的文章,希望能够帮到有需要做二次开发的人。
二次开发也就是我们常说的封装selenium,或者做框架。但是一个框架要包含丰富的类和方法。要有一套完整的体系来帮助我们进行封装。可以说框架的设计思想就是整个框架的灵魂,如果设计思想很正确也就意味着这个框架成功了一半,剩下的就是我们怎么样用程序实现这个思想,在开发的过程中我们也许会用到一些设计模式和引用一些开源框架。这些只是一个开发人员或者程序设计者的基本素质。至于如果把selenium能够有效的封装和一些基本思想,我们来详细的了解一下。 在这篇文章里面只针对selenium的webdriver来进行讨论,我们不再对rc做任何的解释和说明。我们都知道webdriver的使用过程中,贯穿始终的就是一个driver, 并且这个driver代表了一个浏览器的当前窗口,我们进行操作的过程中只是进行当前窗口的操作,也就是最这个current window进行的一系列的操作,如果我们需要对打开的新的window来进行操作的话,我们需要switchTo,包括操作frame,当然整个流程下的操作确实让我们觉得不是很难编写,但是我们编写脚本的过程中需要用到的一些辅助功能可能就会很难的编写,比如最大化浏览器,视角移动到操作的元素等等,这个过程一次编写我们可以做到,但是反复的编写的话肯定是一个让人很头疼的过程,所以这个时候我们要去封装一些常用的方法,我们有了做一个比较完整的框架的想法,但是我们忽然又意识到了,这样的话,我们需要把driver封装起来,因为整个测试的case都是针对的这个driver,并且只有一个driver,这样子的话我们不允许创造多个的driver,也就意味着我们要把自己编写的小工具类和driver联系起来,并且我们的测试用例case类也需要调用这个driver,其实很简单,我们可以用注入的方式来做,把driver当成tools类的一个属性值,然后注入到我们的case类中,也可以通过set的方法来进行操作。有了这些基础,我们可以防止无限的编写重复的方法,这样我们有了自己的工具类。如果说这就是框架的话,就会显得非常的肤浅,因为我们写的这些方法根本没有任何逻辑可言,只是把需要的方法统统的堆到了一起,所以这个时候我们需要想想用到某些方法来进行分一下层次。 Page类和Window类: PageObject模式我们都知道,就是把资源都放入到page类里面,然后再编写逻辑类。这样的话就可以无限的复用这些资源,这只是笼统的讲了一下设计的思想,至于PageObject到底怎么去实现这些设计呢?我们从webDriver的使用开始入手。webdriver是先从定义浏览器开始的。WebDriver driver = new FirefoxDriver(); 这样我们就定义了一个firefox的浏览器,但是自动化的过程不可能只允许我们把定义浏览器的操作放在框架代码里面,那样的硬编码方式使我们的case不存在可移植性了,如果进行兼容性测试的话,维护起来对这些case的修改量是比较大的,这种硬编码方式是我们不能够进行大量维护的,所以我们需要把定义浏览器的过程完全放在case类里面,就是在我们写测试用例的时候再去编写到底用什么浏览器,防止在编写框架的时候硬编码的形式把浏览器写死在了框架里面。做到多浏览器的可维护性,对于我们进行兼容测试也有一定的帮助,这样的话我们需要对浏览器的选择部分要进行一定的编码设计,来完成浏览器的可选择性。在我们定义完了浏览器之后,这个时候我们也许觉得就是开始查找元素了,但是在这个driver的基础上我们应该发现其实这个时候driver代表的整个页面的操作。但是在页面的操作基础上我们应该意识到还有一个级别的操作,那就是window的操作,就是针对浏览器自身的操作。包括一些基本的返回,向前,最大化,最小化,或者移动到制定元素的位置,调用js等等等,这些方法的级别是出于window级别的,和页面无关的。所以我们应该把这些所有的方法都封装到单独的一个层次中,我们暂且称之为window包中,刚才的浏览器的选择的所有方法我们放browser包中。这样我们设计出了两个层次。下面的设计该如何进行呢?我们知道pageObject的思想是把资源都放入到我们定义的page类里面,所以这个时候我们需要思考了,我们如何设计这里的page类呢?按照pageObject的思想来看,page类应该是我们自己编写的,那样我们的框架是不是就可以放弃编写page类了呢?直接封装一些通用的方法?显然是不对的,对于页面html源码操作的过程中,我们烦透了这些元素查找的硬编码方式,在一个case里面或者两个case里面反复的调用编写是让人头疼的一件事情,所以我们可以把所有的单页面看做一个层次,里面和PageObject的思想一样,就是放入了通用的方法,但是它存在的意义不只是这样简单,因为我们操作的过程中需要涉及到frame。并且页面和页面之间涉及到不同window之间的切换,所以如何协调window和page之间的关系成为了我们需要注意的难点和重点,我们知道webdriver里面有一个方法叫做getWindowHandlers,这个方法可以获得所有的句柄,我们想设计这个page类那么意味着我们需要去完美的配合window类,他们之间唯一的关联就是这个方法,所以我们可以设计一个概念,叫做页面集合,在创建window对象的时候我们就会自动的出现一个页面集合器,它的功能就是管理所有的在操作过程中打开的页面。并且能够指定一个特殊的page,就是当前页面。也就是webdriver中的driver对象,因为它一直都是针对当前页面编程的。那样我们的window类里面就存在了两个属性,一个收集器,一个当前页。这样我们在window的级别上就能够完全操作page类了,这样我们在case类设计的时候,只需要通过window类的级别进行编码就可以了,完全可以把page类当作一种资源来处理,我们所有需要的东西都是通过page来获取的。page类的设计中我们一定要编写通用的方法。比如获取title等等等,最主要的一点就是要进行frame的处理操作,我们如何把frame完美的结合在page里面呢?我们在普通的webdriver脚本编写过程中可能反反复复的switchTo的方法让我们很恼火,并且很难让我们理解这些脚本到底是想表达什么意思呢?所以我们需要进行对page进行层次上的小分级,就是把page类再分为一个frame的类和一个模块的类,因为一个大型页面里面,通用的结构和模块是很多的。我们单独的定义一个模块类,可以让这些相同的结构复用在里面的方法。定义frame类主要是处理把定位到frame的操作从case类中脱离出来,我们编写到page类里面或者frame类里面,提供一种方式或者方法来进行frame的定位就可以了,简单实用,并且简化case类的编写,毕竟case类并不是由设计者来编写,尽量做到最简化。当然我们可以成这整个层次都是page类,放在page包里面。 Element类: Element类就是我们常用的driver.findElement()的那种形式,就是元素类,什么是元素类呢?元素就是我们需要定位的那些东西,我们在操作过程中很难知道一个findElement到底查找的是什么,因为所有的标签形式都是通过id或者各种各样的定位方式来实现的。这个时候我们需要把各种各样的findElement都统统的放在一起,造成的脚本难以理解让后续接手的脚本开发人员可能头疼不已,所以我们可以做一些简单的加工。当然我们还要做的一个最大的工作就是元素查找的封装。Element和Page类如何关联起来。我们知道page和webElement的关联就是一个driver.findElement的方法。这样就可以在页面上查找元素了。所以我们也在element类中通过这种形式来进行他们之间的关联。我们通过在element类中添加定位方式的形式来进行元素定位和page的关联,我们只需要在编写自己的page类的过程中直接加入element类就可以了,element类提供了所有的关于element的方法,比如鼠标事件和键盘事件,还有更重要的元素定位方法。定位的方法在这里不做任何的推荐,因为每个人的思路不同,实现的方式也不同,我个人比较偏向的做法是做一个xml来进行资源的管理,把所有需要的资源都放入到xml里面,这样我们就可以进行元素的定位了。并且在后期维护中主要维护xml就可以进行对整个脚本进行维护了,不需要我们大量的重新进行源码的分析和修改了。当然这是设计的优化过程,因为定位的实现我们还是需要自己来完成的,我们知道元素的定位方式各种各样,我们怎么来进行管理和定位呢?我们可以通过map的方法作为属性值来进行元素的管理,他的各种定位方法存放在map中,我们需要的时候只需要调一下就可以了。通过观察源码findElement也是通过map的形式来进行元素定位存储的。我们可以借鉴一下源码的实现方式。当然我们完全封装findElement也是可以的。说到这里可能我们有一个问题比较难以解决,那就是层级定位。如果我们只是给element类添加定位方式的话,那么findElement提供的一级一级的定位方式我们就无法应用了,所以在element类中我们必要还要提供findElement的方法进行层级定位。这只是为了把webdriver的所有方法都尽量应用到而已。其实通过xpath的方式我们就可以基本上定位大多数的元素。剩下的内容就是我们对element内容的扩充了。我们可以把元素的更加具体的抽象出来,比如我们把select,table,checkbox等等等的各种html标签元素显式的定义出来,在我们定义一个元素的时候我们能够更加清晰的看到这个元素的含义,我们知道它是一个按钮或者table等等等,这些小元素的操作需要我们自己深入理解和开发,这里不做过多的介绍。 其他类: 通过这些层次的分析我们已经出现一个框架的雏形了,然后我们剩下的设计就是基于完善和优化了。在一个自动化过程中case类是非常重要的,我们需要知道case类运行结束的结果报告和分析,所以case类的运行等等一系列的东西我们都得有统计,这些东西必须要我们提供一些类来实现,不过所幸的是,强大的junit或者testng完全可以取代我们要去做的工作,他们可以很完美的提供这些功能,我们只需要介入这些开源包就可以了。我们使用QTP的过程中我们经常会用到参数化,我们自动化的设计都有了,但是没有参数化的功能怎么办?我们应该先想象一下数据提供的方式。testng提供了一种参数化的形式,但是它是需要在xml里面配置或者硬编码的形式来进行编写。不过它提供了一种dataprovider的方式来进行参数化,它传递参数的形式是Object[][],我们可能希望使用参数的时候通过excel表格来完成,这些都是可以实现的,poi包提供了解析excel的功能,非常的强大。我们可以自己编写解析类来进行参数化的功能编写,具体实现不再过多去说。调用的方式就简单多了,硬性的记住几个注解就可以了。case类的各种运行我们都有了,还需要一些什么扩展呢?很显然就是日志的扩展。日志的设计也是很有技巧的。我们需要用日志监控某一些方法的话,如果前期没有直接加入日志功能,我们可以通过spring的方式来进行日志切入操作。但是我们在观察日志的时候我们通常会希望知道到底运行到哪一步了,我们可以想一想,所有的操作都是基于element或者window的,page的只是一个抽象出来的概念,所以我们只需要把日志加入到每一个element的方法和window的主要方法里面就可以监控到整个运行的过程,毕竟我们不能够去亲自盯着屏幕一直。这样没个方法不外乎就是运行成功和失败,所以我们可以通过这种方式来进行编码。日志的实现我们可以通过通过的log4j或者自己编写一个小的日志系统。都是可行的方案。 扩展类: 也许我们需要这些系统能够有良好的可移植性,我们可以自己编写类加载器,为以后做整个自动化的测试平台做准备。最主要都是我们做的这些操作可能需要越来简单,所以我们可能会因为引入注解的方式来提供编码效率,所以我们还需要为注解类做一些辅助的工作。当然这些注解的开发需要我们做足足够的需求研究,并不是无谓的去开发各种注解。这些注解的应用应该说很广泛,不再多说注解的好处。并且注解还有一点可应用的地方就是放在数据库的操作中,在自动化测试中,其实数据库的测试也是一个大的难点。在这里我们只讨论前端自动化的设计,不过多的讨论别的东西。 前面讲到的这些类的存在形式其实就是在框架里面的一种层次,我们谈论的这些都是基于webdriver的,并且主要基于前端自动化测试的,当然自动化测试不只包括这些,还包括服务器端,接口自动化,单元自动化等等。我个人的能力水平也是很有限,可能很多地方说到的不是很到位,希望能够通过这篇文章能够给那些希望学习自动化,希望编写小测试框架的童鞋,一点点的启发。如果真的有需要想了解的更多,可以留言给我~~互相交流~