关于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

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

3. 尽量少用sleep

 现在大多数的Web应用程序是使用Ajax技术。当一个页面被加载到浏览器时, 该页面内的元素可以在不同的时间点被加载。这使得定位元素变得困难, 如果元素不再页面之中,会抛出 ElementNotVisibleException 异常。有的人使用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创建一条广告,再在界面上进行检查。这样就能化繁为简了。

下面有我近几年的收集和整理,整体是围绕着【软件测试】来进行整理的,主体内容包含:python自动化测试专属视频、Python自动化详细资料、全套面试题等知识内容。
在这里插入图片描述
对于软件测试的的朋友来说应该是最全面最完整的面试备战仓库,为了更好地整理每个模块,我也参考了很多网上的优质博文和项目,力求不漏掉每一个知识点,很多朋友靠着这些内容进行复习,拿到了BATJ等大厂的offer,这个仓库也已经帮助了很多的软件测试的学习者,希望也能帮助到你

关注微信公众号【程序员二黑】即可领取Python自动化测试超硬核资源啦

猜你喜欢

转载自blog.csdn.net/m0_52650621/article/details/112895250