自动化测试模型介绍


在介绍自动化测试模型之前,我们试着来解释自动化测试库、框架和工具之间的区别。


库的英文单词叫Library,库是由代码集合成的一个产品,供程序员调用。面向对象的代码组织形成的库叫类库,面向过程的代码组织形成的库叫函数库。所以从这个角度来看,我们在第4章介绍的WebDriver就属于库的范畴,因为它提供了一组操作Web页面的类与方法,所以,我们可以称它为Web自动化测试库。


框架的英文单词叫Framework,框架是为解决一个或一类问题而开发的产品,用户一般只需要使用框架提供的类或函数,即可实现全部功能。所以从这个角度来理解unittest框架,它主要用于实现测试用例的组织和执行,以及测试结果的生成。因为它的主要任务就是帮助我们完成测试工作,所以我们通常把它叫做单元测试框架。


工具的英文单词叫Tools,在笔者看来工具与框架所做的事情类似,只是工具会有更高的抽象,屏蔽了底层的代码,一般会提供单独的操作界面供用户操作。例如,Selenium IDE和QTP就是自动化测试工具。


回到自动化测试模型的概念上,笔者认为自动化测试模型可以看作自动化测试框架与工具设计的思想。随着自动化测试技术的发展,演化为以下几种模型:线性测试、模块化驱动测试、数据驱动测试和关键字驱动测试。


下面分别介绍这几种自动化测试模型的特点。


线性测试


通过录制或编写对应用程序的操作步骤产生相应的线性脚本,每个测试脚本相对独立,且不产生其他依赖与调用,这也是早期自动化测试的一种形式:它们其实就是单纯的来模拟用户完整的操作场景。本书第4章所编写的测试脚本就属于线性测试,如图5.1所示。

图5.1  线性测试结构


这种模型的优势就是每一个脚本都是完整且独立的。所以,任何一个测试用例脚本拿出来都可以单独执行。当然,缺点也相当明显,测试用例的开发与维护成本很高:

  • 开发成本很高,测试用例之间可能会存在重复的操作,不得不为每一个用例去录制或编写这些重复的操作。例如每个用例中重复的用户登录和退出操作等。

  • 维护成本很高,正是因为测试用例之间存着重复的操作,所以当这些重复的操作发生改变时,就需要逐一地对它们进行修改。例如登录输入框的定位发生了改变,就需要对每一个包含登录的用例进行调整。


模块化驱动测试


正是由于线性测试的缺陷非常明显,因此早期的自动化测试专家就考虑用新的自动化测试模型来代替线性测试。做法也很简单,借鉴了编程语言中模块化的思想,把重复的操作独立成公共模块,当用例执行过程中需要用到这一模块操作时则被调用,这样就最大限度地消除了重复,从而提高测试用例的可维护性。


如图5.2所示,(需要说明的是,早期的自动化测试以工具为主,而非图5.2所示的代码形式。)模块化的结构很好地解决了线性结构的两个问题:

  • 提高了开发效率,不用重复编写相同的操作脚本。假如,已经写好一个登录模块,后续测试用例在需要登录的地方调用即可。

  • 简化了维护的复杂性,假如登录按钮的定位发生了变化,那么只需修改登录模块的脚本即可,对于所有调用登录模块的测试脚本来说不需要做任何修改。


图5.2  模块化结构



数据驱动测试


虽然模块化驱动测试很好地解决了脚本的重复问题,但是,自动化测试脚本在开发的过程中还是发现了诸多不便。例如,现在我要测试不同用户的登录,首先用的是“张三”的用户名登录;下一个测试用例要换成“李四”的用户名登录。在这种情况下,还是需要重复地编写登录脚本,因为虽然登录的步骤相同,但是登录所用的测试数据不同。


于是,数据驱动测试的概念就为解决这类问题而被提出。从它的本意来解释,就是数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变。这听上去的确是个高大上的概念,而在早期的商业自动化工具中,也的确把这一概念作为一个卖点。对于数据驱动所需要的测试数据,也是通过工具内置 的Datapool管理。


如图5.3所示,数据驱动说的直白点就是数据的参数化,因为输入数据的不同从而引起输出结果的不同。


不管我们读取的是定义的数组、字典,或者是外部文件(excel、csv、txt、xml等),都可以看作是数据驱动,它的目的就是实现数据与脚本的分离。

图5.3  通过脚本读取数据文件


这样做的好处同样显而易见,它进一步增强了脚本的复用性。同样以登录为例,首先是重新设计登录模块,使其可以接收不同的数据,把接收到的数据作为登录操作的 一部分。这样就可以很好地适应相同操作、不同数据的情况。当指定登录用户是“张三”时,那么登录之后的结果就是“欢迎张三”;当指定登录用户是“李四” 时,登录结果就显示“欢迎李四”。这就是数据驱动所希望达到的目的。


关键字驱动测试


理解了数据驱动后,无非是把“数据”换成“关键字”,通过关键字的改变引起测试结果的改变。


目前市面上典型关键字驱动工具以QTP(目前已更名为UFT - Unified Functional Testing)、Robot Framework(RIDE)工具为主。这类工具封装了底层的代码,提供给用户独立的图形界面,以“填表格”的形式免除测试人员对写代码的恐惧,从而降低脚本的编写难度,我们只需使用工具所提供的关键字以“过程式”的方式来编写用例即可。


当然,Selenium家族中的Selenium IDE也可以看作是一种传统的关键字驱动的自动化工具。




上面的脚本由Selenium IDE录制产生,它把每一个动作分为三部分:

  • 做什么?例如打开、输入、点击等动作。

  • 对谁做?通过定位方式找到要操作的对象。

  • 如何做?例如输入框输入的内容为“selenium”等。


当然,关键字驱动技术也在不断发展和进步。下面以功能更为强大的关键字驱动测试框架Robot Framework为例,它也可以像编程一样写测试用例。


1.if分支语句



上面两个条件都不满足

首先,定义a、b两个变量,分别赋值2和5;然后,通过run keyword if关键字判断a是否大于或等于1,如果满足条件则通过log输出“a大于或等于1”,否则继续判断b是否小于或等于5,如果满足条件则通过log输出“b小于或等于5”。如果以上两个条件都不满足,则通过log输出“上面两个条件都不满足”。


2.for循环



这个例子很好理解,for循环i从1到10,每循环一次都通过log输出i的值。


3.读取外部文件



通过Import Resource 关键字读取指定的外部文件。


4.import引入外部类库




通过Import Library关键字引入外部文件。


关键字驱动也可以像写代码一样写用例,在编程的世界中,没有什么不能做;不过这样的用例同样需要较高的学习成本,与学习一门编程语言几乎相当。这样的框架越到后期越难维护,可靠性也会变差,关键字的用途与经验被局限在自己的框架内,你所学到的知识也很难重用到其他地方。所以,从测试人员的经验与技术积累价值来讲,笔者更倾向于直接通过编程的方式开发自动化脚本。


这里简单介绍了几种测试模型的发展过程与特点,虽然是从自动化测试模型的发展顺序逐一介绍的,但它们并非后者淘汰前者的关系。在实际自动化实施过程中,应以项目需求为出发点,综合运用上述模型来开展自动化测试。


模块化驱动测试实例


通过对自动化测试模型的介绍,我们了解了模块化设计的优点。本节我们就以具体的例子来介绍模块的具体应用,当然,使用它的基础是Python语言中函数与类方法的调用。下面以126邮箱为例。


from selenium import webdriver

driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("http://www.126.com")

# 登录
driver.find_element_by_id("idInput").clear()
driver.find_element_by_id("idInput").send_keys("username")
driver.find_element_by_id("pwdInput").clear()
driver.find_element_by_id("pwdInput").send_keys("password")
driver.find_element_by_id("loginBtn").click()

# 收信、写信、删除信件等操作
# ……

# 退出
driver.find_element_by_link_text("退出").click()
driver.quit()


从126邮箱业务流程分析,邮箱所提供的功能都需要登录之后进行,例如收信、写信、删除信件等操作。对于手工来说,测试人员在执行用例的过程中可以一次登录后验证多个功能再退出,但自动化测试的执行有别于手工测试的执行,需要保持测试用例的独立性和完整性,所以每一条用例在执行时都需要登录和退出操作。这个时候就可以把登录和退出的操作封装为公共函数。当每一条用例需要登录/退出时,只需调用它们即可,从而消除代码重复,提高脚本的可维护性。


下面对登录和退出进行模块封装。


from selenium import webdriver

# 登录
def login():
    driver.find_element_by_id("idInput").clear()
    driver.find_element_by_id("idInput").send_keys("username")
    driver.find_element_by_id("pwdInput").clear()
    driver.find_element_by_id("pwdInput").send_keys("password")
    driver.find_element_by_id("loginBtn").click()


# 退出
def logout():
    driver.find_element_by_link_text("退出").click()
    driver.quit()

driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("http://www.126.com")
login()  # 调用登录模块

# 收信、写信、删除信件等操作
# ……

logout()  # 调用退出模块


现在将登录的操作步骤封装到login()函数中,把退出的操作封装到logout()函数中,对于用例本身只需调用这两个函数即可,可以把更多的注意力放到用例本身的操作步骤中。


当然,如果只是把操作步骤封装成函数并没简便太多,我们需要将其放到单独的脚本文件中供其他用例调用。


class Login():

    # 登录
    def user_login(self, driver):
        driver.find_element_by_id("idInput").clear()
        driver.find_element_by_id("idInput").send_keys("username")
        driver.find_element_by_id("pwdInput").clear()
        driver.find_element_by_id("pwdInput").send_keys("123456")
        driver.find_element_by_id("loginBtn").click()

    # 退出
    def user_logout(self, driver):
        driver.find_element_by_link_text("退出").click()
        driver.quit()


当函数被独立到单独的脚本文件中时做了一点调整,主要是为函数增加了浏览器驱动的入参。因为函数实现的操作需要通过浏览器驱动driver,driver需要通过具体调用的用例给定。


from selenium import webdriver
from public import Login

driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("http://www.126.com")

# 调用登录模块
Login().user_login(driver)

# 收信、写信、删除信件等操作
# ……

# 调用退出模块
Login().user_logout(driver)


首先,需要导入当前目录下public.py文件中的Login()类,在需要的位置调用类中的user_login()和user_logout()函数。这样对于每个用例的编写与维护就方便了很多。


本文节选自《Selenium 2自动化测试实战——基于Python语言》一书虫师 编著电子工业出版社出版

相关文章
相关标签/搜索