51Testing软件测试论坛
标题:
UI自动化测试,看看这篇文章再动手
[打印本页]
作者:
一月蔷薇_456
时间:
2018-4-13 14:00
标题:
UI自动化测试,看看这篇文章再动手
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. 控件与用例分离
先看一条登录的测试用例:
'''登录
'''
browser = webdriver.Chrome()
# 打开页面
browser.get("http://bugui.test.tui.qq.com/")
# 点击登录按钮
login_button = browser.find_element_by_xpath("//a[@class='login_btn fR']")
login_button.click()
# 账号密码登录按钮
browser.find_element_by_id('switcher_plogin').click()
# 输入账号
browser.find_element_by_id('u').send_keys(123456)
# 输入密码
browser.find_element_by_id('p').send_keys(123456)
# 登录
browser.find_element_by_class_name('login_button').click()
复制代码
在测试用例中直接指定控件的id或者class_name来查找元素,然后进行操作,这存在三个问题:
(1)假如开发修改了某个元素的id或者class_name,那么相关的测试用例都需要修改,维护成本太大
(2)通过class_name或者id操作控件,使得代码的可读性太差,如果没有注释,别人根本看不懂这段长
长的代码是在干嘛
(3)登录只是所有用例的前提条件,比如创建广告的前提是先登录,加入所有的登录都有这么一段登录的
代码,那用例会非常长
怎么解决呢?再看看下面的代码,将页面中的空间抽象出来:
'''
登录页面抽象类
'''
class LoginPage(object):
'''
登录页面
'''
def __init__(self,driver):
'''返回一个控件字典
'''
if driver is None:
raise WebDriverException("浏览器不能为空")
self.driver = driver
self.Controls={}.fromkeys(('登录按钮','账号密码登录按钮','QQ号输入框','密码输入框', '登录智汇推'))
# 控件抽象
self.Controls['登录按钮']=self.driver.find_element_by_xpath("//a[@class='login_btn fR']")
self.Controls['账号密码登录按钮'] = self.driver.find_element_by_id('switcher_plogin')
self.Controls['QQ号输入框'] = self.driver.find_element_by_id('u')
self.Controls['密码输入框'] = self.driver.find_element_by_id('p')
self.Controls['登录智汇推'] = self.driver.find_element_by_class_name('login_button')
把登录的操作封装起来,实例化LoginPage,对登录页面的控件进行操作:
def login_tui(tui_url, qq_num, qq_psw):
'''登录智汇推的公共函数'''
browser = webdriver.Chrome()
browser.get(tui_url)
login_page = LoginPage(browser)
login_page.Controls['登录按钮'].click()
login_page.Controls['账号密码登录按钮'].click()
login_page.set_qq_num(qq_num)
login_page.set_qq_psw(qq_psw)
login_page.Controls['登录智汇推'].click()
return browser
复制代码
作者:
一月蔷薇_456
时间:
2018-4-13 14:00
当任一个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创建一条广告,再在界面上进行检查。这样就能化繁为简了。
欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/)
Powered by Discuz! X3.2