基于Selenium与Pytest框架的Web UI自动化测试系统的设计与实现

随着互联网的高速发展,软件技术日新月异,产品更新换代的加快等,始终都离不开一个最核心的要素就是保证产品的质量,测试人员则在其中担任着不可或缺的角色。测试人员的主要工作职责就是通过各种测试手段去发现软件潜在的漏洞,最终保证产品质量。但随着敏捷开发的盛行,适用于解决传统手工回归测试效率低的痛点的自动化测试技术也越来越受到测试人员的重视。本文所探讨的就是软件自动化测试框架的实现,首先是对需求进行分析,然后通过对比国内外成熟的自动化测试框架技术进行技术选型,最终确定使用基于Python语言的,结合Selenium
摘要由CSDN通过智能技术生成

摘要

随着互联网的高速发展,软件技术日新月异,产品更新换代的加快等,始终都离不开一个最核心的要素就是保证产品的质量,测试人员则在其中担任着不可或缺的角色。测试人员的主要工作职责就是通过各种测试手段去发现软件潜在的漏洞,最终保证产品质量。但随着敏捷开发的盛行,适用于解决传统手工回归测试效率低的痛点的自动化测试技术也越来越受到测试人员的重视。本文所探讨的就是软件自动化测试框架的实现,首先是对需求进行分析,然后通过对比国内外成熟的自动化测试框架技术进行技术选型,最终确定使用基于Python语言的,结合Selenium Webdirver和Pytest的框架技术,本文阐述了该框架的架构设计、脚本的开发调试、环境的搭建部署、代码的运行监控、报告的生成等功能的实现过程。该框架目前运行良好,且切切实实的提高了软件自动化回归测试的效率。

关键词

自动化测试、Python、Selenium、PO设计模式

1、引言

1.1、开发背景

任何软件都需要通过严格的测试,确定其满足了产品需求,且可以正常运作了才能投入市场。所以软件测试的使命就是应尽可能的发现软件存在的漏洞,且能够适应快速迭代的需求,能在短时间内高效的,低成本的完成,软件测试技术也将面临不小的挑战。质量团队需要鉴于测试行业流行的行业标准,吸收国内外先进的测试技术,以寻求符合自己团队的测试方案。
传统的手工测试仍然是一种高质量的测试手段,其重要程度不容忽视,但相应的也存在一定的缺点,比如:有限的人力无法满足快速迭代时大量的回归测试需求、无法实现24小时随时随地测试等等。因此我们需要在功能测试的基础上,通过自动化的测试手段去弥补我们人工不能做到的测试执行工作。
自动化测试的实现我们可以选择国内外成熟的测试辅助工具,也可以自己开发,都各自有优缺点,需要根据当下的质量团队,和测试需求综合考量。

1.2、开发意义

作为软件工程质量管理领域里面一个非常重要的节点-软件测试自动化,在国内已日趋走向成熟化,而Web UI自动化测试虽然作为分层测试(测试金字塔)中的最顶层,但我们却不能忽视其在整个软件生命周期中质量保证环节中所起到的重要作用。
在高速发展的互联网时代,通过Web端输出的软件产品仍占据了大半壁江山,我们将在此探讨针对Web UI自动化测试技术的设计与实现,Web UI自动化测试的原理即是:模拟人工在Web网站上的行为和操作,以发现潜在的产品漏洞。

自动化测试的优点有:

  • 替代大量重复性的手工操作,为测试人员节省大量的时间,把精力放在设计测试用例和探索性测试上。
  • 非常适用敏捷开发的团队,可以大幅度提升回归测试的效率。
  • 可以无人值守测试,把自动化测试放在非工作时间执行,而工作时间测试人员只需要对测试结果进行分析即可。

但自动化测试也存在缺点:

  • 不能完全取代手工测试
  • 对被测系统的变化无法做到随机应变
  • 前期开发工作量大,成本大于收益
  • 仅能发现回归测试的缺陷
  • 运行过程的不稳定
  • 业务测试人员不具备脚本开发能力时,需要额外增加自动化测试人员。

基于自动化测试的优缺点,我们可以分析得出自动化测试的适用场景如下:

  • 需求趋于稳定,至少UI不会频繁变动的场景
  • 敏捷开发模式下版本迭代频繁,需要频繁执行回归测试的场景
  • 在多个系统、浏览器上重复运行相同测试的场景
  • 手工测试无法满足、或成本太高的场景
  • 团队中有具备编程能力的测试人员的场景

综上所述:本次探讨的Web UI自动化测试技术,也是基于满足自动化测试的适用场景的一个或多个前提下进行的,毕竟满足自身需求的才是最好的。利用一切可以利用的人力、资源和技术,保障产品质量稳定,是本次Web UI自动化测试开发的意义所在。

2、需求分析

当产品趋于稳定,进入优化迭代的时候,越来越多的不会频繁变动的功能需求也将带来大量重复的回归任务,此时我们就可以考虑引入自动化测试,而模拟人工在Web端的行为和操作,我们可以通过自动化测试工具来实现,或者测试团队完全自己开发一套Web UI自动化测试框架,或者对国内外成熟的自动化测试工具进行二次封装等,但无论通过何种方式,我们首先需要分析和确定我们这个工具,或者这套框架需要实现的功能需求是什么。

我们的Web UI自动化测试方案需要实现的需求如下:

2.1、功能需求

  • 要便于实施和维护。要充分考虑测试团队成员的能力配比。如果这套框架由专业的自动化测试人员开发,后续需要非自动化测试人员去执行和维护的话,那么该框架就要做到让非专业人员也能快速实施和维护
  • 要有自我修复能力。比如自动化测试脚本本身存在问题了,可以通过异常处理和场景恢复等方法进行自我修复
  • 高可复用性。封装一些重复的,通用的功能,对外只提供接口,哪里需要用直接引用模块和接口方法即可,避免重复造轮子,降低脚本开发工作量
  • 可快速定位问题。对于一个自动化测试框架来说比较重要的还有日志的记录,我们需要采集测试用例本身生成的日志,还有测试环境上产生的日志,便于自动化脚本运行失败后能够快速定位问题所在
  • 要实现测试业务与测试数据的解耦。数据包括测试数据、配置数据等,我们需要提供一套统一的外部数据组织规范和访问接口,数据存储的格式可以考虑xml、json、yaml、csv等
  • 执行测试用例时支持灵活选取用例。自动化测试有几个典型的应用场景包括:冒烟测试、回归测试、定时任务测试等,不同的应用场景需要测试执行时支持灵活切换指定特定的测试用例或全量用例等
  • 浅显易读的测试报告。能够随时产出方便查看的测试报告,便于实时进行结果分析和错误处理
  • 持续集成和持续测试。现在的敏捷开发都是采用CI/CD手段来实现持续集成和交付,作为自动化测试环节也不能独立开来,需要与团队的CI/CD无缝衔接,以达到一键或自动触发构建。

2.2、稳定性需求

一个好的自动化测试方案除了满足以上基本的功能需求外,还需要具备可靠的稳定性。如果一套开发完成且投入使用的自动化测试脚本在运行过程中频繁出错,需要花费额外的人力去排查问题和修复脚本,那这种自动化测试将失去它最初的意义了

  • 我们需要在前期开发和调试阶段就要确保脚本是可以跑通的,通过增加异常处理等手段来避免脚本本身的不稳定
  • 我们还需要考虑到一些客观的因素,比如网络不稳定等情况时,通过等待机制、或失败用例重跑机制等来增强脚本的健壮性

3、技术选型

自动化测试的需求确定好后,我们接下来就需要考虑技术选型的问题了,我们需要根据自身的需求,再综合对比现在国内外成熟的自动化测试技术,最终确定适合自己团队的自动化测试方案最优选。

UI自动化测试的技术方案通常分为控制(控制客户端)、执行(运行通过特定API编写的测试用例)、结果上报这几个主要组成部分,同时还应该考虑这三部分所普适的编程语言。

3.1、编程语言

关于编程语言的选择,本文探讨的自动化测试方案中选择的是目前主流的编程语言Python,其优点如下:

  • 简单:Python对测试脚本开发人员来说入门简单,测试人员无需去搞明白语言本身,只需要专注于解决问题即可
  • 兼容性:Python是跨平台语言, Python的跨平台是语言自身的特性决定的,在很多平台上直接写Python代码就可以运行
  • 丰富的库:Python有自己的类库,而且标准库还很庞大。python有可定义的第三方库可以使用
  • 可读性:Python采用强制缩进的方式使得代码具有极佳的可读性。

3.2、控制方案

要实现Web UI自动化测试,我们就采用一种工具来实现控制客户端进行模拟人工操作,大致的操作流程就是打开目标网页,定位到目标位置,进行点击,输入等操作。目前市面上相关的工具有:Selenium、Cypress、Playwright、Puppeteer等,下面我们大致了解一下这些工具的特点:

  • Selenium,主流自动化技术,有Selenium IDE进行快速录制脚本、也可以结合Webdriver提供的API来实现自动化
  • Cypress,是基于JS实现的一个框架,可以录制,也可以自主编辑脚本,被称作后Selenium时代的产物,但用起来其实没有Selenium那么好用,目前市场应用不算多
  • Playwright,基于Node实现的自动化测试框架,脱离了Webdriver,支持主流语言,使用简单,对于新手较友好度较高,很多技术类博主都有推荐过,但目前业内应用也不多
  • Puppeteer,是Chrome开发团队在 2017 年发布的一个Node.js包,用来模拟Chrome浏览器的运行,提供了一系列API,通过Chrome DevTools Protocol 协议控制,默认情况下是以headless启动Chrome的,也可以通过参数控制启动有界面的Chrome。

通过综合对比:Selenium的方案最为传统,也是目前最常见的浏览器控制方法。Selenium通常需要和Webdriver配合使用,Selenium通过Webdriver控制浏览器,再对上层执行层暴露API或sdk。同时 Selenium也提供standalone server的方案,允许执行层通过调用标准restful API控制浏览器,在这种模式下对执行层的编程语言和运行时都没有任何限制,这也是 Selenium 生态繁荣的重要原因。Selenium的API封装遵循 W3C 提供的Webdriver标准,因此Selenium对各大主流浏览器的支持都不错,如果测试场景对浏览器兼容性有较高的要求,需要在多种浏览器中执行测试用例,Selenium仍是首选。同时由于Selenium已经发展多年,各种解决方案也更为完善。例如并行方案 Selenium Grid,可以支持多节点的用例负载均衡;还有在CI场景下官方维护的各种Docker Image等。

所以本文选用的控制客户端进行模拟人工操作的工具选择了目前较为成熟和流行的Selenium。

3.3、执行方案

自动化测试的执行方案的核心在于选取一套最优的自动化测试框架。
首先来理解一下什么是框架?框架是系统的可重用设计,代码的组织和运行控制问题就是通过框架来解决的。在编写自动化测试脚本时,我们配置文件的读取,数据文件的读取,日志的记录等各种各样的方法在我们编写自动化测试脚本时,不可能需要用时就写一遍,多次用就写多遍,这样无形中会增加我们的工作量。所以我们需要提取出公共的方法,进行单独封装,放到公用模块里。同时我们需要将配置文件,数据文件,日志等存放到独立的文件夹中。这种对公共方法的封装及对脚本及配置文件怎么组织的设计就叫做框架。
我们可以将框架总结如下几个方面:

  • 公共方法封装
  • 组织代码及配置文件
  • 控制执行

那何为测试框架?一个完整的测试脚本包含的步骤有以下几个:环境准备、业务操作、结果断言、环境销毁。而测试框架一般还要完成的功能有:用例加载,批量执行,异常控制,结果输出等。测试框架应具有的特点:

  • 易用性:编写用例,执行用例,生成报告及定位问题方便
  • 健壮性:稳定,比如timeout机制等
  • 扩展性:丰富的插件
  • 灵活性:用例组织或执行的灵活性,Fixture功能(不同范围的setUp和tearDown)等
  • 定制性:二次开发方便

当下基于Python语言的比较流行的测试框架有:Unittest、Pytest、Nose、Robot Framework。我们先来做个简单的对比:
在这里插入图片描述

由上面对比表格分析得出:
Unittest: Python自带,最基础的单元测试框架
Nose: 基于Unittest开发,易用性好,有许多插件
Pytest: 同样基于Unittest开发,易用性好,信息更详细,插件众多
Robot Framework:一款基于Python语言的关键字驱动测试框架,有界面,功能完善,自带报告及log清晰美观
总体来说,Unittest比较基础,二次开发方便,适合高手使用;Pytest/Nose更加方便快捷,效率更高,适合小白及追求效率的公司;Robot Framework由于有界面及美观的报告,易用性更好,灵活性及可定制性略差。

综上所述,最终确定选择Pytest,测试框架应具备的特点Pytest都满足了。

3.4、结果上报方案

前面我们在选取执行方案时,对比了不同的测试框架,框架本身基本都具备输出html测试报告的功能,但都存在不够美观的缺点。而allure-pytest是Pytest的一个插件,比传统的Pytest/Unitest 生成的html报告更加优美。我们来看看官网对Allure的介绍:

  • Allure 框架是一种灵活的轻量级多语言测试报告工具,不仅可以以简洁的Web报告形式非常简洁地显示已测试的内容,也允许参与开发过程的每个人从日常测试中提取最大程度的有用信息
  • 从dev/qa的角度来看,Allure报告可以缩短常见缺陷的生命周期:可以将测试失败划分为bug和被中断的测试,日志、步骤、fixture、附件、计时、执行历史以及与TMS和BUG管理系统集成,所以,通过以上配置,所有负责的开发人员和测试人员可以尽可能的掌握测试信息
  • 从管理者的角度来看,Allure提供了一个清晰的“大图”,其中包括已覆盖的特性、缺陷聚集的位置、执行时间轴的外观以及许多其他方便的事情
  • Allure的模块化和可扩展性确保您始终能够微调某些东西,以使Allure更适合您
  • Allure 生成的报告样式简洁美观,同时又支持中文。Allure还支持使用Jenkins工具持续集成,整套环境搭建下来以后,使用起来非常方便。

毫无疑问,Allure是我们作为结果上报方案的最佳选择。

4、系统设计

4.1、架构设计

架构设计的基本任务就是回答:框架如何实现?因此框架设计又称为概要设计。经过前面需求分析和技术选型阶段的分析,我们已经得到了目标框架应该完成的功能和通过什么技术去完成。
对于一个优秀的框架,不可或缺的当属是分层思想,而在Web UI自动化测试中,PO模式即Page Object是十分流行的一项技术了。PO是一种设计模式,提供了一种页面元素定位和业务操作流程分离的模式。当页面元素发生变化时,只需要维护对应的page层修改定位,不需要修改业务逻辑代码。
PO核心思想是分层,实现脚本重复使用,易维护,可读性高,主要分三层:
对象库层:Base(基类),封装page 页面一些公共的方法,如初始化方法、查找元素方法、点击元素方法、输入方法、获取文本方法、截图方法等。
操作层:page(页面对象),封装对元素的操作,一个页面封装成一个对象
业务层:business(业务层),将一个或多个操作组合起来完成一个业务功能。比如登录:需要输入帐号、密码、点击登录三个操作。

基于分层思想和PO设计模式,我们可以设计出如下基本的框架模型:

  • cases测试用例层:存放所有的测试用例
  • common公共层:存放一些公共的方法,如封装page页面基类、捕获日志等
  • datas测试数据层:存放测试数据,用yaml文件进行管理
  • logs日志层:存放捕获到的所有日志和错误日志,便于问题定位
  • pages页面对象层:存放所有页面对象,一个页面封装成一个对象
  • reports测试报告层:存放产出的测试结果数据,失败截图
  • run用例执行层:存放测试执行文件
  • pytest.ini:pytest框架自带配置文件,如修改用例收集规则,标签,命令行参数等。
  • requirements.txt:记录当前项目的所有依赖包及其精确版本号,以便后续迁移项目使用。
    示例如下图:
    在这里插入图片描述

4.2、详细设计

4.2.1、common公共层

公共层的主要任务就是实现通用功能的封装,比如封装一个公共的page类,用于被其他page继承,其需要对外提供的方法有:元素的定位、元素的操作(如鼠标点击、输入文本、鼠标悬浮等)、二次封装隐式等待、页面操作(如窗口切换)等。还可以建一个utils文件,专门在里面封装一些公共的工具类,比如日志捕获、读取配置文件、发送邮件、数据库操作、获取路径等。

basepage.py文件的代码示例如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :basepage.py
@Auth : luoluo
@Description:
"""
import json
from time import sleep, strftime, localtime, time
from typing import Dict, List
import allure
import yaml
from selenium.webdriver import ActionChains
from selenium.webdriver.remote.webdriver import WebDriver
from common.utils import get_logger


class BasePage():
    """
    公共page类,用于被其他page继承
    """
    # 动态传参参数,定义一个字典,要替换的内容放在字典里
    params = {
   }
    # 定义一个弹框黑名单列表,后续处理
    black_list = []
    # 定义最大查找次数
    max_num = 10
    # 定义异常次数
    error_num = 0
    # 定义入口url
    # base_url = ''
    # 实例化logger
    logger = get_logger()

    def __init__(self, driver: WebDriver = None):
        self.driver = driver
        self.driver.maximize_window()
        self.driver.implicitly_wait(8)

    def setup_implicitly_wait(self, timeout):
        """
        自定义隐式等待时间
        :param timeout: 等待时间
        :return:
        """
        self.driver.implicitly_wait(timeout)

    def switch_to_window(self, index):
        """
        切换到指定窗口
        :param index: 窗口索引
        :return:
        """
        handles = self.driver.window_handles
        self.driver.switch_to.window(handles[index])

    def find(self, by, locator):
        """
        查找元素
        :param by: 定位方式
        :param locator: 定位表达式
        :return:
        """
        try:
            element = self.driver.find_element(by, locator)
            self.error_num = 0
            return element
        except Exception as e:
            self.logger.error(f'未找到目标元素: {
     locator}{
     e}')
            # 捕获到异常时进行截图,并保存到指定screenshots文件夹中,文件按当前时间命名
            st = strftime('%Y-%m-%d_%H-%M-%S', localtime(time()))
            file_name = '../reports/screenshots/' + st + '.png'
            self.driver.get_screenshot_as_file(file_name)
            # 异常截图添加到allure测试报告中
            allure.attach.file(file_name, attachment_type=allure.attachment_type.PNG)
            # 设置最大查找次数
            if self.error_num > self.max_num:
                self.error_num = 0
                self.setup_implicitly_wait(10)
                raise e
            # 每次进except一次都执行+1操作
            self.error_num += 1
            # 黑名单中的弹框处理
            for ele in self.black_list:
                self.logger.info(f'查找黑名单元素')
                # find_elements 会返回元素的列表[ele1,ele2,...],如果没有元素则返回一个空列表
                eles = self.driver.find_elements(*ele)
                print(f'查找到的黑名单元素有:{
     eles}')
                if len(eles) > 0:
                    self.logger.info(f'处理黑名单元素{
     ele[0]}')
                    eles[0].click()
                    # self.driver.execute_script('arguments[0].click();', ele[0])
                    # ActionChains(self.driver).click(ele[0]).perform()
                    # 弹框异常都处理完了,再次查找元素
                    self.logger.info(f'find:by = {
     by}, locator = {
     locator}')
                    # return self.driver.find_element(by, locator)
                    return self.find(by, locator)
            # 如果黑名单都处理完,还没有找到想要的元素,则抛出异常
            raise e

    def find_click(self, by, locator):
        """
        查找元素并点击
        :param by: 定位方式
        :param locator: 定位表达式
        :return:
        """
        self.logger.info(f'find_click:by = {
     by}, locator = {
     locator}')
        self.find(by, locator).click()

    def find_send(self, by, locator, text):
        """
        查找元素并输入
        :param by: 定位方式
        :param locator: 定位表达式
        :param text: 输入文本
        :return:
        """
        self.logger.info(f'find_send:by = {
     by}, locator = {
     locator}, text = {
     text}')
        self.find(by, locator).send_keys(text)

    def find_js_click(self
  • 18
    点赞
  • 148
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值