第六章 自动测试实施

  从本章开始,我们将讲述如何实施自动化测试,在第一章的时候,我们也提供了自动化实施的步骤。那些儿步骤是指导方针,可以按着这一步步地去实施,可是有点儿笼统。本章我们将从具体实例入手,按前面的我们提到的步骤来讲解,通过本章的学习,你可以从一个被测试的网站着手,从零开始建立起普通的回归测试用例。

6.1 评审被测试对象功能

      现在你的老大给你分配个任务:我们公司的业务已经成熟,网站主要功能也基本上不会变化了,我们想在每次上线后自动回归一下主功能。所以呢,我们想让你对我们的网站建立起自动化测试,用例要求覆盖主要功能。

当你接到这个任务的时候,你应该如何去做呢?不是去想用什么来写自动化,或是马上用你擅长的语言马上就去写测试脚本。而是要先分析一下被测网站的功能,哪些儿是主要的,哪些儿必须自动化,哪些儿不能自动化。

现在我们以众筹网(www.zhongchou.cn)为例,分析一下主要功能:

1)登录注册

2)浏览项目

3)搜索项目

4)查看项目详情

5)支持项目

6)个人信息设置

7)发起项目

8)查看自己相关的项目

9)发起的项目管理和订单发货

10)消息中心

等等,这十个主要功能是网站最基本的功能,然后我们分析一下哪些儿能自动化,哪些儿不能?

能自动化的部分:12346810

不能自动化的部分:579

   为什么不能自动化呢?5,支持项目,涉及到真实的支付,影响项目对账,因为不管是测试环境还是线上环境,都是真实的支付,如果有支付的测试环境,也是可以自动化的。

   7,发起项目,发起项目通过审核后,会在线上产生测试数据,影响用户体验。这违反了测试不能对线上产生垃圾数据的原则,所不不建议自动化,但是可以测试发起项目的流程,不对项目进行上线。

   9,发起的项目管理和订单发货。发起项目我们就没有自动化,所以这块功能是没有数据的,再者订单发货会短信通知支持者,会影响到用户,不建议自动化。

   当你接到任何一个测试任务的时候,需要先这样分析一下被测试对象的功能及是否合适自动化测试。然后查找一下是否存在对应功能的手工测试用例,一般公司都会做测试用例的收集工作的。如果没有,就先编写对应的测试用例,然后再去转化成自动化测试用例。

6.2 评审被测对象编码

评审完了主功能,然后开始评审被测试对象的编码,选择与被测试对象相同或是同一系列的脚本语言来写测试用例。一般网站编码无非是ASP.NET,PHP,JSP等,编写测试用例的脚本语言也可以用php,python,java等,根据实际情况,做出相应的选择。

经查看,我们的众筹网是用php编写的,按原则来说,我们应该用php来编写测试用例。Webdriver也支持php,但是php支持库不多,在用例组织和报告生成等方面也存在着不足,所以目前用php编写自动化测试用例的不多。综合考虑各种因素,我们决定用python来编写自动化测试用例。

6.3 自动化测试框架的选择

   目前业界做页面自动化的都是选用selenium1.0 RC或是selenium2.0(webdriver),而它们之间是存在着区别的:

Selenium RC 工作原理:

(1)RC server 在服务端启动 浏览器 并将Core 注入到浏览器中 (为了解决浏览器的同源策略)

(2)我们的测试脚本调用Client API,Client将操作转化成标准的selenese语句发送给RC Server。

(3)Selenium Core 解释selenese语句,通过js的方式操作浏览器。

Webdriver 工作原理:

(1)WebDriver 启动目标浏览器,并绑定到指定端口。该启动的浏览器实例,做为web driver的remote server。

(2)Client 端通过CommandExcuter发送HTTPRequest 给remote server 的侦听端口(通信协议: the webriver wire protocol)。

(3)Remote server 需要依赖原生的浏览器组件(如:IEDriver.dll,chromedriver.exe),来转化转化浏览器的native调用。

   那么remote server端的这些功能是如何实现的呢?答案是浏览器实现了webdriver的统一接口,这样client就可以通过统一的restful的接口去进行浏览器的自动化操作。目前webdriver支持ie, chrome, firefox, opera等主流浏览器,其主要原因是这些浏览器实现了webdriver约定的各种接口。

优缺点:

(1)RC 需要启动一个 RCserver,就直观感觉上多了一个步骤。

(2)RC 采用js 的方式,稳定性和兼容行方面还是取决与js的代码的质量。有时需要自己去写js代码来扩展相应的功能。由于自己是从RC用起的,对于RC js的方式还是比较接受,感觉比较灵活。

(3)至于弹出对话框和警告框的处理,RC原生是不支持的。不过可以通过第三方软件来解决,比如autoit。我用的就是autoit,使用简单方便,是一个不错的工具。

   通过上面的分析,结合现在的时代潮流,我们采用高版本的selenium 2.0来作为我们的自动化测试的框架。

6.4 自动化测试用例运行环境

由于我们采用的是python开发的脚本语言,对运行环境要求不是太高,可是相应的包还是要安装的。在脚本编写期间,我们一般是在自己机器上编写和调试的,运行环境是windows环境。在后期的交付运行,自动执行回归的时候,我们要放到服务器上,服务器是linux环境的。

Windows环境和linux环境下,python运行环境差别不大,所以不要太担心环境的问题。可是也有需要注意的地方,比如说文件路径的写法,两个环境下还是不同的。这也是我们一再要求用相对路径,最好用函数来获取路径,而不是把路径写死到代码中的原因。

6.5 自动化代码架构的规划

任何一个好的程序,都需要有好的规划。考虑到现在的结构化程序设计,我们的自动化测试代码也需要好好规划一下架构,不能像记流水帐似的按测试用例的步骤罗列下来。

6.5.1 代码架构规划的原因

自动化测试用例的主要工作就是回归测试,由于其任务的特殊性,就决定了自动化测试用例是成体系的,大里的自动化测试用例放在一起,如果不好好规划一下,会给后期的工作带来很大的影响。

代码规划的原因:

   1)增加可读性。根据用途,将代码进行分类存放,方便团队其他成员进行代码管理与合作开发。

   2)便于维护。自动化测试用例不可能是一成不变的,被测试的网站变化了,我们对应的自动化测试用例也要进行相应的维护。如果不进行规划,出现问题后不方便定位,更加会耗费大量的成本去维护。

  3)提高代码利用率。结构化程序设计,要求我们对代码重复利用率要高,同样的代码要写在公用函数或是方便,减少代码量。

6.5.2 如何规划代码架构

目前自动化的用法主要有两种:1,简单录制回放转化成的测试用例,此用例用于反复测试的功能点,一旦功能上线后便不再用。目地是加快回归测试的执行。特点就是对功能进行反复测试,不考虑通用性,健壮性等。2,用于回归测试的系统化的测试用例。此种用例是需要反复执行的,一定要考虑程序的通用性,健壮性以及执行时间等。

针对第一种测试用例,虽然使用时间比较短,通用率也不高,不过有些儿公司还是会收集这样的测试用例的,以便以后同样的或是类似的功能回归测试的时候用。此时可以按业务线对测试用例进行存放,测试用例中包含测试脚本和测试数据。

回归测试自动化测试用例是我们的重点,所以我们要详细规划一下:

1)为了增加代码的通用性,我们把大部分测试用例需要执行的操作步骤封装成函数,放到公共的类中,并把公共的类也按功能进行分类,统一放到一个文件夹下,如CommonFunction.

2)将所有的测试用例放到一个文件夹下,测试用例与测试数据分离开,以便网站有变化的时候,我们只需要修改相应该的测试数据即可。测试用例放到一个文件夹下,如:TestCases

3)测试数据放到和测试用例同名的Xml文件中,然后通过公用的数据读取方法,对测试数据进行读取,然后在测试用例中调用通用的函数,读取到的数据传递过去。所有数据存放到统一的文件夹下,如:TestData.

4)测试用例集的存放。由于不同的测试目的,我们会运行不同的测试用例。如果测试目的A,我们将运行测试用例123;测试目的B ,我们运行测试用例1,3,4,5。所以我们会有不同的测试用例集文件,将这些儿文件统一存放到一个文件夹下,如:TestSuites.

5)其他的资源文件,可以创建不同的文件夹来进行存放,如图片文件夹image.

   所以,经过规划后,我们的测试用例结构如图6.5.2所示,图中的文件是我先前写的测试用例。下面的章节,我们会具体讲述测试用例:

6.5.2自动化测试用例代码结构规划

6.6 编写自动化测试用例

     我们规划好了代码架构,就可以编写具体的测试用例了。首先,我们写登录的测试用例,在写自动化测试用例的时候,我们要先写一下公用函数类。根据需要,我们写了三个通用的类放到CommonFunction文件夹下:WebDriverHelp用来存放所有页面操作用到公用方法;QT_Operations用来存放具体操作功能块,如登录,退出等;DataOperations用来存放所有数据操作,用来读取xml中的测试数据。

然后我们在TestCases文件夹下编写登录的测试用例TestCase_QT_Login.py执行具体的测试操作,验证测试用例执行是否成功。

并把测试用例需要的测试数据,放到TestData文件夹下TestCase_QT_Login.xml文件中。如果网站有所变化,可以修改这个里面的测试数据,从而减少代码维护的成本。

6.6.1 WebDriverHelp类的内容

WebDriverHelp是我们所有测试用例能用到的方法,可以在编写测试过程中不断抽象出来,写到这个类里面,相当于对Webdriver再次做了一下封装。

下面是部分代码,以供大家参考:

@author:songxianfeng

@copyright: V1.0

'''

import time 

import datetime

from selenium import webdriver

fromselenium.webdriver.support.ui import Select

 

fromselenium.common.exceptions import NoSuchElementException

fromselenium.webdriver.common.action_chains import ActionChains

fromselenium.webdriver.common.keys import Keys

global G_WEBDRIVER,G_BROWSERTYTPE,DRIVER

 

class WebDriverHelp(object):

    '''

    本类主要完成页面的基本操作,如打开指定的URL,对页面上在元素进行操作等

    '''

 

    def  __init__(self,btype="close",atype="firefox",ctype="local"):

        '''

        根据用户定制,打开对应的浏览器

        @parambType: 开关参数,如果为close则关闭浏览器

        @paramaType:打开浏览器的类型,如chrome,firefox,ie等要测试的浏览器类型

        @paramcType:打开本地或是远程浏览器: local,本地;notlocal:远程       '''

        global DRIVER

        if( btype == "open" ):            

            if( atype == "chrome" ):

                if(ctype == "local"):    

                    DRIVER = webdriver.Chrome()

                   DRIVER.maximize_window()

                elif(ctype == "notlocal"):                 

                    DRIVER =webdriver.Remote(command_executor='http://124.65.151.158:4444/wd/hub',desired_capabilities=webdriver.DesiredCapabilities.CHROME)

                   DRIVER.maximize_window()                   

            elif( atype == "ie" ):

                if(ctype == "local"):  

                    DRIVER =webdriver.Ie()

                   DRIVER.maximize_window()

                elif(ctype == "notlocal"):                   

                    DRIVER =webdriver.Remote(command_executor='http://124.65.151.158:4444/wd/hub',desired_capabilities=webdriver.DesiredCapabilities.INTERNETEXPLORER)

                    DRIVER.maximize_window()                    

            elif( atype == "firefox" ):

                if(ctype == "local"): 

                    DRIVER =webdriver.Firefox()

                   DRIVER.maximize_window()

                elif(ctype == "notlocal"):                   

                    DRIVER =webdriver.Remote(command_executor='http://10.20.5.56:4444/wd/hub',desired_capabilities=webdriver.DesiredCapabilities.FIREFOX)

                   DRIVER.maximize_window()  

                                   

        self.DRIVER = DRIVER

 

    def  setup(self,logintype):

        '''

        定制测试URL

        @paramloginplace: 指定测试的URL: qiantai:前台测试地址,houtai:后台测试地址,zhengshi:正式环境测试地址

        ysh:原始会测试地址 zhengshiysh:正式原始会测试地址

        '''

        try:

            qiantai_url = "http://test.zhongchou.cn"

            ysh_url = "http://test.ysh.zhongchou.cn"  

            houtai_url = "http://test.admin.zhongchou.cn"

            zhengshi_url = "http://www.zhongchou.cn"

            zhengshi_ysh_url = "http://ysh.zhongchou.cn"

 

            if(logintype=="qiantai"):

                self.DRIVER.get(qiantai_url)

            elif(logintype=="houtai"):

                self.DRIVER.get(houtai_url)

            elif(logintype=="zhengshi"):

                self.DRIVER.get(zhengshi_url)

            elif(logintype=="ysh"):

                self.DRIVER.get(ysh_url)

            elif(logintype=="zhengshiysh"):

                self.DRIVER.get(zhengshi_ysh_url)

            else:

                print'路径错误!'

            self.DRIVER.implicitly_wait(1)

        except NoSuchElementException:

            print'您选择的测试地址出错!!'      

    def  teardown(self):

        '''

        关闭浏览器

        '''        

        self.DRIVER.quit() 

                

    def  geturl(self,url):

        '''

        打开指定的网址

        @param url:要打开的网址

        '''

        self.DRIVER.get(url)    

def  selectvalue(self,findby,select,selectvalue):

        '''

        通过定制定位方法和要选择项的文本,选择指定的项目

        @paramfindby:定位方法,如:byid,byname,byclassname等

        @paramselect: 要执行选择操作的下拉框句柄

        @paramselectvalue: 下拉框中要选择项的文本

        '''

        if(findby == 'byid'):

            select = Select(self.findelementbyid(select))

        elif(findby =='byname'):

            select = Select(self.findelementbyname(select))

        elif(findby =='byclassname'):

            select = Select(self.findelementbyclassname(select))

       select.select_by_visible_text(selectvalue)

       

    def  inputvalue(self,findby,elmethod,value):

        '''

        通过定制定位方法,在输入框中输入值

        @paramfindby: 定位方法,如:byid,byname,byclassname,byxpath等

        @paramelmethod: 要定位元素的属性值,如:id,name,class name,xpath等

        @paramvalue: 要给文本框输入的值

        '''

        if(findby == 'byid'):

            self.findelementbyid(elmethod).send_keys(value)

        elif(findby == 'byname'):

            self.findelementbyname(elmethod).send_keys(value)

        elif(findby =='byclassname'):

            self.findelementbyclassname(elmethod).send_keys(value)

        elif(findby == 'byxpath'):

            self.findelementbyxpath(elmethod).send_keys(value)

      ……  

上面的代码都有很详细的注释,在此就不一一讲述了。

6.6.2 QT_Operations类的内容

QT_Operations类是业务相关的公用功能块的封装,以便增加函数的公用性,减少代码量。例如,登录,在很多测试用例的第一步都需要先登录再操作的。所以你可以抽像出测试用例中的功能模块,封装后放到这个类中。

QT_Operations类的部分代码展示:

#-*- coding: UTF-8 -*-

'''

Created on2014-12-15

@author:songxianfeng

@copyright: V1.0

'''

import time

import win32api

import win32con

 

fromWebDriverHelp importWebDriverHelp

 

class QT_Operations(object):

    '''

    众筹前台相关操作

    '''

    def login(self,userName,passwd):

       '''

        从首页直接登录

        @param userName: 用户名

        @param passwd:密码

        @param type1:指示登录方式,1为从主页登录,2,从登录页登录

        '''

       WebDriverHelp().clickitem("byclassname", "Js-showLogin")

       time.sleep(3)

       WebDriverHelp().clearvalue('byname','username')

       WebDriverHelp().inputvalue('byname','username',userName)

       WebDriverHelp().clearvalue('byname','user_pwd')

       WebDriverHelp().inputvalue('byname','user_pwd',passwd)

       time.sleep(1)

       WebDriverHelp().clickitem("byclassname", "a-btn") 

       time.sleep(5)

       

    def logout(self):

       '''

        退出登录

        '''

       WebDriverHelp().geturl("http://www.zhongchou.cn/usernew-loginout")

……

展示部分只包含了登录和退出功能,其他的功能可以根据测试用例的需要进行添加。

6.6.3 DataOperations类的内容

   DataOperations类是对测试数据进行读取的操作,我们是用xml来存放测试数据的,所以测试用例执行的时候,需要先将测试数据读取出来,传递给相应的函数来对测试用例进行执行。然后根据执行的结果,判断测试用例是否执行成功。

DataOperations的内容:

 

#-*- coding: UTF-8 -*-

'''

Created on 2014-12-15

@author:songxianfeng

@copyright: V1.0

'''

import MySQLdb

from xml.dom import minidom

global DOC,CONN

 

class DataOperations(object):    

    '''

    数据读取相关操作

    '''

  

    def __init__(self,filename):

        '''

        初始化xml文档

        '''

        global DOC,CONN

        DOC = minidom.parse("../testData/"+filename)  

          

    def readxml(self,ftagname,num,stagname):

        '''

        从指定的文件中中读取指定节点的值

        @paramftagname:起始节点的名称,如:project 

        @param num:取与起始节点相同的第num个节点

        @param stagname:起始节点下的二级节点

        @return: 返回二级节点的值

        '''           

        root = DOC.documentElement

       message=root.getElementsByTagName(ftagname)[num]

        return message.getElementsByTagName(stagname)[0].childNodes[0].nodeValue

    

    def readxml_attribute(self,ftagname,num,stagname,attributeName):

        '''

        从all_case.xml文件中读取节点的属性值

        @paramftagname:起始节点的名称,如:project 

        @param num:取与起始节点相同的第num个节点

        @paramstagname: 起始节点下的二级节点

        @paramattributeName: 二级节点的属性名

        @return:返回二级节点指定的属性值       

        '''

       

        root = DOC.documentElement

       message=root.getElementsByTagName(ftagname)[num]

       returnmessage.getElementsByTagName(stagname)[0].getAttribute(attributeName)

python对xml的读取操作,如果你不太明白,可以去自行学习相关的内容,要教程不讲解相关的操作。

6.6.4 具体的测试用例

  上面的公用方法类创建以后,我们就可以着手编写具体的测试用例了。在TestCases文件夹下创建测试用例TestCase_QT_Login.py,然后编写下面的测试步骤:

(1)           打开众筹网。

(2)           点击登录按钮,输入用户名和密码。

(3)           验证是否登录成功,用户昵称是不是刚刚登录的账号。

(4)           退出登录,关闭浏览器。

测试用例代码如下:

-*- coding: UTF-8 -*-

'''

Created on2014-12-15

@author:songxianfeng

@version: v1.0 

'''

import unittest

import time

#导入需要的公共函数类

fromCommonFunction.WebDriverHelp import WebDriverHelp

fromCommonFunction.DataOperations import DataOperations

fromCommonFunction.QT_Operations import QT_Operations

class testcases_login(unittest.TestCase):

    '''

    登录检测

    '''

    def setUp(self):

       WebDriverHelp("open","firefox","local").setup("zhengshi")#打开浏览器,并打开众筹网

       

    def testlogin(self):        

       #登录检测        

       dataoper=DataOperations("TestCase_QT_Login.xml") #读取测试数据

       #登录       

       QT_Operations().login(dataoper.readxml('login', 0, 'username'),

dataoper.readxml('login', 0, 'password'))

       self.assertEqual(WebDriverHelp().gettext('byxpath',dataoper.readxml('login', 0, 'checkpoint1')),dataoper.readxml('login', 0, 'value1')) #判断登录是否成功

       #退出

       QT_Operations().logout()

       time.sleep(5)

       self.assertEqual(WebDriverHelp().gettext('byclassname',dataoper.readxml('login', 0, 'checkpoint2')),dataoper.readxml('login', 0, 'value2')) #判断退出是否成功

                        

    def tearDown(self):

       WebDriverHelp().teardown()#关闭浏览器

       

if __name__ == "__main__":    

unittest.main()

   经过我们上面的封装,现在具体的测试用例只是传参数,调用具体的函数,验证执行结果。是不是非常简单?有点儿像我们小时候玩积木,用一块块现成的积木堆积出魔幻的城堡。

6.6.5 测试用例的具体数据

   由于我们把测试用例和测试数据完全分离开了,所以我们用和测试用例同名的文件名命名对应的测试数据文件。登录测试用例的数据文件是TestCase_QT_Login.xml,具体内容如下:

<?xmlversion="1.0" encoding="UTF-8"?>

<TestData>

    <login name="登录测试数据">

      <username>183*****905</username>

      <password>*******</password>

      <checkpoint1>//div[@id='Js-header-loginBtn']/span/i[2]</checkpoint1>

      <value1>潜龙0318</value1>

      <checkpoint2>Js-showLogin</checkpoint2>

      <value2>登录</value2>

    </login>

</TestData>

我们将要验证的数据,获取验证元素的Xpath都写到这个里面,这样就算网页有所变化,我们只需要改这个数据文件中对应的Xpath即可,不需要更改测试用例。这样可以将网站变化影响到测试用例降到最低,同时也减少了我们维护测试用例的成本。

编写完上面所有的代码后,我们只要右击测试用例,选择Run as->python run,调试运行测试用例即可。

6.7 本章小结

     本章我们讲解了如何从接触到一个网站开始,从零着手去编写自动化测试用例。通过上面各个方法的考虑,选择合适的框架,脚本语言,规划代码架构,编写公用函数,并以众筹网的登录注册为例,我们编写了具体的测试用例。通过这样的讲解,相信大家一定能编写测试用例了,要建立测试用例集,不过是按照先前我们选择的测试用例,全部转化成测试用例代码即可。第七章我们将讲述测试用例集的组织,以及将测试用例放到代码管理工具jenkins上实现自动化运行。从编写自动化测试用例,讲述到实现无人值守的回归测试用例的建立,希望你能学到点儿实用的东西。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值