51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1526|回复: 1
打印 上一主题 下一主题

UI自动化测试,看看这篇文章再动手

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2018-4-13 14:00:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
UI自动化测试作为测试人员的基本技能,拥有ROI(投入产出比)低、维护成本高、稳定性差等等特点。面对这
些难题,本文以尝试寻找解决方案,希望对大家有用。

     本文首先列举UI自动化过程中普遍会遇到的问题,然后逐个的解决这些问题。

一、UI自动化的世纪难题

1. ROI(投入产出比)低

       做过UI自动化的同学肯定都会遇到这个问题,辛辛苦苦写好的测试用例,跑了还没几天,新的需求之后,
开发把页面改了,原来定位的控件失效了,N条测试用例跑不通了。每条用例改完之后,UI又变了,又得改,
很奔溃有没有。



2. 维护成本高

           自动化测试脚本构造的越快,维护成本也就越大。有些测试通过录制回访来创建测试用例。快速的构造
成百上千条测试用例。维护测试用例编变成一项繁重的任务,比如一个UI节点细微的变化,可能导致自动化工
具没有识别UI控件,那么所有用到这个控件的测试用例都需要更新,查找替换并且保证没有替换错就是一个很
大的工作量,更别说一般录制的脚本人工都不容易理解。再比如运行完测试用例之后,需要花费大量的时间排
查错误,错误有脚本错误,有功能的变更,有bug等。逐个排查和解决需要耗费大量的人力。

3. 稳定性差

         某条测试用例本地调试的时候是通的,批量执行的时候就失败了。

         某条用例上次跑通了,怎么这次又失败了。。。

         web界面上明明有这个元素,怎么就找不到呢?

         深究这些问题的原因,无非有以下几个原因:

(1)代码里用了sleep,但是不同网络环境下,页面加载时间不一样,批量执行的时候,加载时间比较长,
sleep已经结束了,还没加载成功,就跑下一语句了。导致了用例失败。

(2)通过图像识别来定位按钮等空间,不同的机器分辨率不一样,识别不出按钮控件了

        针对这些难点,在动手进行UI自动化之前,需要考虑清楚两个问题:一是什么时候进行UI自动化测试,
二是该怎么做自动化测试。

二、UI自动化测试的先决条件

1. UI趋于稳定

        通过前文分析UI自动化的维护成本可以看出,维护工作量跟UI变动是否频繁有很大的关系。测试人员在
介入UI自动化前,需要确定UI功能和流程是否稳定了。若UI功能和流程已经稳定了再开始进行UI自动化。对
于一个系统来说,可以不需要等整个系统都稳定了再介入,可以评估某一个独立功能UI稳定之后,对这个功
能进行UI自动化测试,在运行的过程中优化框架和测试用例。然后再等待其他UI稳定,之前的经验就可以用
到后续的功能中。

        当然介入UI自动化不是说一股脑把所有的功能都用自动化实现,后续会降到如何设计自动化测试用例。

2. 大量的UI重复操作

        若UI功能已经稳定,但是针对这个UI的测试次数很少,进行UI自动化测试的ROI会很低。比如某一个功
能手动指定需要1小时的时间,UI自动化投入需要8小时(编写自动化用例、每次运行的维护),这个自动化
只执行了5次。相当于自动化投入的时间比人工测试投入的时间还多了3小时。所以需要重复进行操作的UI比
较适合做UI自动化测试,可以通过UI自动化测试把测试人员从繁重的功能测试中释放出来,进行更有意义的
工作。

         怎么评估UI是否适合自动化测试呢?可以在进行自动化测试前,要评估自动化测试的ROI,确保ROI比
较高,才比较适合进行UI自动化测试。

三、如何进行UI自动化测试

1. 测试用例的设计

       在进行UI自动化测试之前,我们要先明确UI自动化测试的目的是什么?如果是为了验证界面上的每个
控件的颜色、排布。那么这样的自动化用例越多就会越不稳定,因为控件受测试机的分辨率、尺寸等影响,
在一台机器上运行成功了,另一台机器上可能就失败了,在同一台机器这次成功了,下次就可能失败了。
所以我们要明确UI自动化测试并不是为了发现更多的bug,而是为了保证产品的质量。举个例子,web页
面登录QQ,如果我们把登录按钮的布局颜色都检查一遍,其实是没有必要的,我们只需要通过UI操作来
登录QQ,能登录成功,表示登录的功能是正常的。假如开发在改动代码的过程中影响到了登录功能,导
致登录失败,我们就能通过这条自动化用例发现问题了。

        所以,把主流程的测试用例实现成自动化用例即可。也即实现冒烟测试用例即可。

2. 控件与用例分离

       先看一条登录的测试用例:

  1. '''登录
  2. '''
  3. browser = webdriver.Chrome()
  4. # 打开页面
  5. browser.get("http://bugui.test.tui.qq.com/")
  6. # 点击登录按钮
  7. login_button = browser.find_element_by_xpath("//a[@class='login_btn fR']")
  8. login_button.click()
  9. # 账号密码登录按钮
  10. browser.find_element_by_id('switcher_plogin').click()
  11. # 输入账号
  12. browser.find_element_by_id('u').send_keys(123456)
  13. # 输入密码
  14. browser.find_element_by_id('p').send_keys(123456)
  15. # 登录
  16. browser.find_element_by_class_name('login_button').click()
复制代码

在测试用例中直接指定控件的id或者class_name来查找元素,然后进行操作,这存在三个问题:

(1)假如开发修改了某个元素的id或者class_name,那么相关的测试用例都需要修改,维护成本太大

(2)通过class_name或者id操作控件,使得代码的可读性太差,如果没有注释,别人根本看不懂这段长
长的代码是在干嘛

(3)登录只是所有用例的前提条件,比如创建广告的前提是先登录,加入所有的登录都有这么一段登录的
代码,那用例会非常长

怎么解决呢?再看看下面的代码,将页面中的空间抽象出来:

'''
登录页面抽象类
'''
  1. class LoginPage(object):
  2.     '''
  3.          登录页面
  4.     '''
  5.     def __init__(self,driver):
  6.         '''返回一个控件字典
  7.         '''
  8.         if driver is None:
  9.             raise WebDriverException("浏览器不能为空")
  10.         self.driver = driver
  11.         self.Controls={}.fromkeys(('登录按钮','账号密码登录按钮','QQ号输入框','密码输入框', '登录智汇推'))
  12.         
  13.         # 控件抽象
  14.         self.Controls['登录按钮']=self.driver.find_element_by_xpath("//a[@class='login_btn fR']")
  15.         self.Controls['账号密码登录按钮'] = self.driver.find_element_by_id('switcher_plogin')
  16.         self.Controls['QQ号输入框'] = self.driver.find_element_by_id('u')
  17.         self.Controls['密码输入框'] = self.driver.find_element_by_id('p')
  18.         self.Controls['登录智汇推'] = self.driver.find_element_by_class_name('login_button')
  19. 把登录的操作封装起来,实例化LoginPage,对登录页面的控件进行操作:

  20. def login_tui(tui_url, qq_num, qq_psw):
  21.     '''登录智汇推的公共函数'''
  22.     browser = webdriver.Chrome()
  23.     browser.get(tui_url)
  24.     login_page = LoginPage(browser)
  25.     login_page.Controls['登录按钮'].click()
  26.     login_page.Controls['账号密码登录按钮'].click()
  27.     login_page.set_qq_num(qq_num)
  28.     login_page.set_qq_psw(qq_psw)
  29.     login_page.Controls['登录智汇推'].click()
  30.     return browser
复制代码


分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

该用户从未签到

2#
 楼主| 发表于 2018-4-13 14:00:52 | 只看该作者

当任一个id出现调整时,只要修改LoginPage这一个类的内容即可,维护成本降低。通过中文名字命名每个
控件,测试用例可读性增强。同时封装功能的方法,用例中只要调用方法即可,使得用例更加简洁,便于维护。

3. 尽量少用sleep

         现在大多数的Web应用程序是使用Ajax技术。当一个页面被加载到浏览器时, 该页面内的元素可以
在不同的时间点被加载。这使得定位元素变得困难, 如果元素不再页面之中,会抛出 ElementNotVisibleEx
ception 异常。有的人使用sleep方法来等待页面加载成功,比如sleep(5),假入某一次运行网络不稳定,
页面加载了10秒才成功,那这个sleep就没用了。那你可以说我sleep(30)再进行下一步操作不就得了,
但是这样的话用例的执行时间变长了很多,对于手动执行没有什么优势了。

         怎么减少sleep呢,有两个方法:

(1)WebDriverWait

使用selenium进行web自动化测试可以使用WebDriverWait方法,具体可google使用方法:

# 当页面元素可见后,表示页面加载成功
self.flag_frame = 'ui_ptlogin'
locator = (By.NAME, self.flag_frame)
WebDriverWait(self.driver, 20).until(EC.frame_to_be_available_and_switch_to_it(locator))
非web的UI自动化测试,可以自己设计一些waitForInvisiable之类的方法,等待控件加载成功。

(2)TimeOut方法增加重试逻辑

selenium执行下拉框选择时,有一定的概率会失败,在这里增加重试逻辑,会提升用例的稳定性,举一个重试逻辑的例子:

class Timeout(object):
'''Timeout类,实现超时重试逻辑'''

def __init__(self, timeout = 10, interval = 0.5):
'''
@param timeout:超时描述,默认是10
@param interval:重试时间间隔秒数,默认是0.5
'''

self.timeout = float(timeout)
self.interval = float(interval)

def retry(self, func, args, exceptions=(), resultmatcher=None, message=""):
"""多次尝试调用函数,成功则并返回调用结果,超时则抛出异常。
:param func: 尝试调用的函数
:type args: dict或tuple
:param args: func函数的参数
:type exceptions: tuple类型,tuple元素是异常类定义,如QPathError, 而不是异常实例,如QPathError()
:param exceptions: 调用func时抛出这些异常,则重试。
如果是空列表(),则不捕获异常。
:type resultmatcher: 函数指针类型
:param resultmatcher: 函数指针,用于验证第1个参数func的返回值。
默认值为None,表示不验证func的返回值,直接返回。
其函数原型为:
def result_match(ret): # 参数ret为func的返回值
pass
当result_match返回True时,直接返回,否则继续retry。
:return: 返回成功调用func的结果
"""
start = time.time()
waited = 0.0
try_count = 0
while True:
try:
try_count += 1
if dict == type(args):
ret = func(**args)
elif tuple == type(args):
ret = func(*args)
else:
raise Exception("args type %s is not a dict or tuple" % type(args))

if resultmatcher == None or resultmatcher(ret) == True:
print "%s Timeout尝试次数: " % message, try_count
TestLog.log_info("%s Timeout尝试次数: %s" % (message, str(try_count)))
return ret
except exceptions:
pass
waited = time.time() - start
if waited < self.timeout:
time.sleep(min(self.interval, self.timeout-waited))
elif try_count == 1:
continue
else:
raise Exception("在%d秒里尝试了%d次" % (self.timeout, try_count))
print try_count

4. 脚本中不使用坐标和图像识别

         selenium对控件的找寻提供了很多方法,尽量通过id或者class_name来查找控件。但有一种情况是动
态布局条件的id都是一样的,无法区别。我的经验是通过text的方法查找:

driver.find_element_by_xpath("//*[text()='%s']" % string)
5. 保证用例的独立性

       尽量保证一条测试用例只做一件事情,而且用例与用例之间没有关联关系,这样能提升用例的稳定性

6. 能不用UI的地方尽量不用UI操作

        比如用例的目的是为了检查广告信息的展现功能,需要新建一条广告,通过界面新建广告特别繁杂,
可以通过API创建一条广告,再在界面上进行检查。这样就能化繁为简了。
回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-17 20:44 , Processed in 0.067396 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表