摘要

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

关键词

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

目录

  • 1、引言
    • 1.1、开发背景
    • 1.2、开发意义
  • 2、需求分析
    • 2.1、功能需求
    • 2.2、稳定性需求
  • 3、技术选型
    • 3.1、编程语言
    • 3.2、控制方案
    • 3.3、执行方案
    • 3.4、结果上报方案
  • 4、系统设计
    • 4.1、架构设计
    • 4.2、详细设计
      • 4.2.1、common公共层
      • 4.2.2、pages页面对象层
      • 4.2.3、cases测试用例层
      • 4.2.4、datas测试数据层
      • 4.2.5、run测试执行层
      • 4.2.6、logs日志层
      • 4.2.7、reports测试报告层
      • 4.2.8、pytest.ini配置文件
      • 4.2.9、requirements.txt版本库文件
  • 5、环境部署
  • 6、持续测试
  • 7、系统调优
    • 7.1、系统运行不稳定问题
    • 7.2、系统执行时间过长问题
    • 7.3、系统多环境切换问题
  • 8、系统评价

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_loggerclass BasePage():"""公共page类,用于被其他page继承"""# 动态传参参数,定义一个字典,要替换的内容放在字典里params = {}# 定义一个弹框黑名单列表,后续处理black_list = []# 定义最大查找次数max_num = 10# 定义异常次数error_num = 0# 定义入口url# base_url = ''# 实例化loggerlogger = get_logger()def __init__(self, driver: WebDriver = None):self.driver = driverself.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_handlesself.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 = 0return elementexcept 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 = 0self.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 edef 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, by, locator):"""查找元素并通过js点击:param by: 定位方式:param locator: 定位表达式:return:"""self.logger.info(f'find_js_click:by = {by}, locator = {locator}')ele = self.find(by, locator)self.driver.execute_script('arguments[0].click();', ele)def find_hover(self, by, locator):"""查找元素并鼠标悬浮在上方:param by: 定位方式:param locator: 定位表达式:return:"""self.logger.info(f'find_hover:by = {by}, locator = {locator}')ele = self.find(by, locator)ActionChains(self.driver).move_to_element(ele).perform()sleep(3)def parse_action(self, path, fun_name):"""解析行为:param path:存放了测试步骤的yaml文件路径:param fun_name:行为方法名:return:"""with open(path, 'r', encoding='utf-8') as f:function = yaml.safe_load(f)steps: List[Dict] = function[fun_name]raw = json.dumps(steps)# 动态传参params处理for key, value in self.params.items():raw = raw.replace("${" + key + "}", value)steps = json.loads(raw)for step in steps:if step['action'] == 'find':self.find(step['by'], step['locator'])elif step['action'] == 'find_click':self.find_click(step['by'], step['locator'])elif step['action'] == 'find_send':self.find_send(step['by'], step['locator'], step['text'])elif step['action'] == 'find_js_click':self.find_js_click(step['by'], step['locator'])elif step['action'] == 'find_hover':self.find_hover(step['by'], step['locator'])

utils.py文件中关于日志捕获的代码示例如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :utils.py
@Auth : luoluo
@Description:存放公共方法:如日志记录、发送邮件、数据库操作、获取路径等
"""
import logging
import logging.handlers
import datetime# 【日志配置】
def get_logger():"""捕获日志"""logger = logging.getLogger('mylogger')logger.setLevel(logging.DEBUG)rf_handler = logging.handlers.TimedRotatingFileHandler('../logs/all.log', when='midnight', interval=1, backupCount=0,atTime=datetime.time(0, 0, 0, 0))rf_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))f_handler = logging.FileHandler('../logs/error.log')f_handler.setLevel(logging.ERROR)f_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))logger.addHandler(rf_handler)logger.addHandler(f_handler)return logger

4.2.2、pages页面对象层

页面对象层,是以一个页面为一个对象,单独对每个页面的业务操作进行封装,是Page Object设计模式最核心的特点,其原理是将具体的测试用例和测试业务进行分离,提高了代码的可维护性。

pages页面对象层代码示例1: 首页 main_page.py 文件

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :main.py
@Auth : luoluo
@Description:
"""
from common.basepage import BasePage
from pages.ai_page import AiPage
from pages.article_analysis_page import ArticleAnalysisPage
from pages.column_analysis_page import ColumnAnalysisPage
from pages.column_atlas_page import ColumnAtlasPage
from pages.column_daily_page import ColumnDailyPage
from pages.column_gallery_page import ColumnGalleryPage
from pages.column_vedio_page import ColumnVedioPage
from pages.member_brandfollow_page import MemberBrandfollowPage
from pages.member_userinfo_page import MemberUserinfoPage
from pages.search_picture_page import SearchPicturePage
from time import sleepclass Main(BasePage):"""首页"""file_path = '../datas/main.yaml'def login(self):"""登录"""self.parse_action(self.file_path, 'login')def from_carousel_goto_article_page(self):"""点击首页轮播图推荐位,跳转主题详情页"""self.parse_action(self.file_path, 'from_carousel_goto_article_page')return ArticleAnalysisPage(self.driver)def from_data_analysis_goto_article_page(self):"""点击首页数据分析推荐位,跳转主题详情页"""self.parse_action(self.file_path, 'from_data_analysis_goto_article_page')return ArticleAnalysisPage(self.driver)def from_nav_goto_analysis_column_page(self):"""点击导航中分析类栏目"""self.parse_action(self.file_path, 'from_nav_goto_analysis_column_page')return ColumnAnalysisPage(self.driver)def from_nav_goto_atlas_column_page(self):"""点击导航中图集类栏目"""self.parse_action(self.file_path, 'from_nav_goto_atlas_column_page')return ColumnAtlasPage(self.driver)def from_nav_goto_gallery_column_page(self):"""点击导航中图库类栏目"""self.parse_action(self.file_path, 'from_nav_goto_gallery_column_page')return ColumnGalleryPage(self.driver)def from_nav_goto_daily_column_page(self):"""点击导航中DailyTrends栏目"""self.parse_action(self.file_path, 'from_nav_goto_daily_column_page')return ColumnDailyPage(self.driver)def from_nav_goto_vedio_column_page(self):"""点击导航中视频栏目"""self.parse_action(self.file_path, 'from_nav_goto_vedio_column_page')return ColumnVedioPage(self.driver)def from_main_goto_picture_search_page(self):"""从首页点击【图片库】"""self.parse_action(self.file_path, 'from_main_goto_picture_search_page')self.switch_to_window(-1)sleep(5)return SearchPicturePage(self.driver)def from_main_goto_ai_page(self):"""从首页点击【AI搜图】"""self.parse_action(self.file_path, 'from_main_goto_ai_page')self.switch_to_window(-1)return AiPage(self.driver)def from_main_goto_memger_userinfo_page(self):"""从首页点击头像跳转至会员中心-个人中心页"""self.parse_action(self.file_path, 'from_main_goto_memger_userinfo_page')self.switch_to_window(-1)return MemberUserinfoPage(self.driver)def from_main_goto_memger_brandfollow_page(self):"""从首页点击关注品牌跳转至会员中心-关注品牌页"""self.parse_action(self.file_path, 'from_main_goto_memger_brandfollow_page')self.switch_to_window(-1)return MemberBrandfollowPage(self.driver)

pages页面对象层代码示例2: 分析类文章详情页 article_analysis_page.py 文件

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :article_analysis_page.py
@Auth : luoluo
@Description:
"""
from common.basepage import BasePage
from pages.member_userinfo_page import MemberUserinfoPage
import osclass ArticleAnalysisPage(BasePage):"""分析类文章详情页"""file_path = '../datas/article_analysis.yaml'def switch_page(self):"""切换版面"""self.parse_action(self.file_path, 'switch_page')def from_analysis_article_goto_main(self):"""从分析类文章详情页跳转至首页"""self.parse_action(self.file_path, 'from_analysis_article_goto_main')self.switch_to_window(-1)from pages.main import Mainreturn Main(self.driver)def from_analysis_article_goto_userinfo_page(self):"""从分析类文章详情页跳转至个人中心页"""self.parse_action(self.file_path, 'from_analysis_article_goto_userinfo_page')self.switch_to_window(-1)return MemberUserinfoPage(self.driver)

4.2.3、cases测试用例层

测试用例层的主要功能是调用各类page完成业务流程并进行断言,为了提高我们测试用例可维护性,我们采用PO设计模式,其优点在于:就算UI页面频繁被修改,我们无需去修改用例,而只需去修改对应的page即可。
由于我们是使用Pytest框架来组织我们的测试用例的,所以我们的测试用例层会有一个Pytest框架特有的文件:conftest.py,该文件主要是实现fixture共享的,其作用和特点如下:

  • 储存的都是fixture函数,会封装一些前置和后置操作方法,实现测试用例的环境准备和环境销毁操作,类似于setup和teardown功能;
  • 用例脚本需要使用fixture函数时,无需导入conftest.py这个文件的,框架会直接自动去查找;
  • conftest.py它是属于层级共享的,也就是说,一个自动化项目当中,可以在不同的包下面去创建conftest.py这个文件。

我们将在conftest.py文件里面定义一些通用的fixture函数,比如初始化driver、登陆、窗口切换等,代码示例如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :conftest.py
@Auth : luoluo
@Description:
"""
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import pytest# @pytest.fixture(scope='session', autouse=True)
# def init_driver(base_url):
#     """
#     selenium-grid分布式模式
#     driver初始化和回收,作用于全局
#     :param base_url: 入口url
#     :return: driver
#     操作说明:
#     1、从官网把selenium的jar包下载下来:https://www.selenium.dev/downloads/
#     2、启动hub节点:java -jar selenium-server-standalone-3.141.59.jar -role hub
#     3、启动node节点
#       3.1、通过配置文件node.json来启动node节点
#         3.1.1、先从使用文档中把node配置的json代码复制到文件node.json中,修改为自己想要的浏览器配置
#         3.1.2、另起终端执行命令启动node节点:java -jar selenium-server-standalone-3.141.59.jar -role node -nodeConfig node_win.json
#       3.2、通过命令行参数启动node节点
#         java -jar selenium-server-standalone-3.141.59.jar -role node -port 5555 -hub http://192.168.8.23:4444/grid/register
#         -maxSession 10 -browser browserName=chrome,seleniumProtocol=WebDriver,maxInstances=10 ,platform=WINDOWS,version=92.0.4515.131
#     注意点:node节点一定要配置好python和webdriver的环境变量
#     """
#     hub_url = 'http://127.0.0.1:4444/wd/hub'
#     capability = DesiredCapabilities.CHROME.copy()
#     # capability = DesiredCapabilities.SAFARI.copy()
#     driver = Remote(command_executor=hub_url, desired_capabilities=capability)
#     driver.maximize_window()
#     driver.implicitly_wait(8)
#     base_url = 'https://www.trendtest.com/?gender_id=72105'
#     driver.get(base_url)
#     yield driver
#     driver.quit()@pytest.fixture(scope='session', autouse=True)
def init_driver(base_url):"""driver初始化和回收,作用于全局:param base_url: 入口url:return: driver"""# 用无头浏览器打开chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--disable-gpu')chrome_options.add_argument('--no-sandbox')driver = webdriver.Chrome(chrome_options=chrome_options)# # 用界面浏览器打开# # driver = webdriver.Safari()# driver = webdriver.Chrome()driver.maximize_window()driver.implicitly_wait(8)driver.get(base_url)yield driverdriver.quit()@pytest.fixture(scope='session')
def init_main(init_driver):"""初始化main,作用于全局:param init_driver::return: main"""from pages.main import Mainmain = Main(init_driver)yield main@pytest.fixture(scope='session', autouse=True)
def login(init_driver):"""初始化登录,作用于全局:param init_driver::return: None"""from pages.main import Mainmain = Main(init_driver)main.login()@pytest.fixture(scope='function')
def swhich_latest_win(init_driver):"""测试方法后置条件:切换到当前最新窗口"""yieldhandles = init_driver.window_handlesinit_driver.switch_to.window(handles[-1])@pytest.fixture(scope='function')
def close_active_win(init_driver):"""测试方法后置条件:关闭当前活动窗口"""yieldinit_driver.close()@pytest.fixture(scope='function')
def swhich_close_swhich_win(init_driver):"""测试方法后置条件:切换到最新窗口,关闭当前活动窗口,再切换到最新窗口"""yieldhandles1 = init_driver.window_handlesinit_driver.switch_to.window(handles1[-1])init_driver.close()handles2 = init_driver.window_handlesinit_driver.switch_to.window(handles2[-1])@pytest.fixture(scope='function')
def close_swhich_win(init_driver):"""测试方法后置条件:关闭当前活动窗口,再切换到当前最新窗口"""yieldinit_driver.close()handles = init_driver.window_handlesinit_driver.switch_to.window(handles[-1])@pytest.fixture(scope='function')
def close_swhich_close_swhich_win(init_driver):"""测试方法后置条件:关闭当前活动窗口,再切换到当前最新窗口,再关闭当前活动窗口,再切换到当前最新窗口"""yieldinit_driver.close()handles1 = init_driver.window_handlesinit_driver.switch_to.window(handles1[-1])init_driver.close()handles2 = init_driver.window_handlesinit_driver.switch_to.window(handles2[-1])@pytest.fixture(scope='function')
def swhich_close_swhich_close_win(init_driver):"""测试方法后置条件:切换到最新窗口,关闭当前活动窗口,再切换到最新窗口"""yieldhandles1 = init_driver.window_handlesinit_driver.switch_to.window(handles1[-1])init_driver.close()handles2 = init_driver.window_handlesinit_driver.switch_to.window(handles2[-1])init_driver.close()

cases测试用例层代码示例1:首页测试类test_main_page.py文件

(其中就使用了conftest.py中定义的fixture函数init_main,而初始化driver函数:init_driver和登陆函数:login 因为已设置为session级别,且是自动调用,因此无需再在测试用例中进行引用了)

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :test_main_page.py
@Auth : luoluo
@Description:
"""
import allure
import pytest@allure.feature('首页测试类')
class TestMainPage:@allure.story('测试点击推荐位')@pytest.mark.smokedef test_carousel_recommend(self, init_main):init_main.from_carousel_goto_article_page()@allure.story('测试点击数据分析')def test_data_analysis(self, init_main, swhich_close_swhich_win):init_main.from_data_analysis_goto_article_page()@allure.story('测试点击导航分析类栏目')def test_nav_analysis_column(self, init_main):init_main.from_nav_goto_analysis_column_page()@allure.story('测试点击导航图集类栏目')def test_nav_atlas_column(self, init_main):init_main.from_nav_goto_atlas_column_page()@allure.story('测试点击导航图库类栏目')def test_nav_gallery_column(self, init_main):init_main.from_nav_goto_gallery_column_page()@allure.story('测试点击导航DailyTrends栏目')def test_nav_daily_column(self, init_main):init_main.from_nav_goto_daily_column_page()@allure.story('测试点击【会员头像】')def test_click_me(self, init_main, swhich_close_swhich_win):init_main.from_main_goto_memger_userinfo_page()@allure.story('测试点击【品牌关注】')def test_click_brandfollow(self, init_main, swhich_close_swhich_win):init_main.from_main_goto_memger_brandfollow_page()@allure.story('测试点击【图片库】')def test_click_picutre_search(self, init_main, swhich_close_swhich_win):init_main.from_main_goto_picture_search_page()if __name__ == '__main__':pytest.main()

cases测试用例层代码示例2:分析类主题详情页测试类est_article_analysis_page.py文件

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :test_article_analysis_page.py
@Auth : luoluo
@Description:
"""
import allure
import pytest@allure.feature('分析类文章详情页测试类')
class TestAnalysisArticlePage:@pytest.fixture(scope='class')def init_article(self, init_main):"""测试类前置条件:进入分析类文章详情页测试类后置条件:点击浏览器返回按钮返回首页"""article = init_main.from_nav_goto_analysis_column_page().from_list_goto_analysis_article_page()yield articlearticle.driver.back()article.driver.back()@pytest.fixture(scope='function')def back_article_win(self, init_main):"""测试方法后置条件:点击浏览器返回按钮返回文章详情页"""yieldinit_main.driver.back()@allure.story('测试在分析类文章详情页点击切换版面')def test_switch_page(self, init_article):init_article.switch_page()@allure.story('测试从分析类文章详情页跳转至首页')def test_from_analysis_article_goto_main(self, init_article, back_article_win):init_article.from_analysis_article_goto_main()@allure.story('测试从分析类文章详情页跳转至个人中心页')def test_from_analysis_article_goto_userinfo_page(self, init_article, close_swhich_close_swhich_win):init_article.from_analysis_article_goto_userinfo_page()if __name__ == '__main__':pytest.main()

4.2.4、datas测试数据层

测试数据层,是将测试用例,与测试数据进行分离,通过yaml文件的方式对数据进行管理,实现测试用例的数据驱动和操作步骤的数据驱动。

datas测试数据层代码示例1:首页测试类 所对应的测试数据main.yaml 文件

################### 【登陆】 ########################################################################
login:# 关闭今日热点弹窗- action: find_js_clickby: xpathlocator: //*[@class="ant-modal-close-x"]# 点击我的- action: find_js_clickby: xpathlocator: //*[@class="loginCom_3Gl0e"]/span# 输入用户名- action: find_sendby: xpathlocator: //*[@name="username"]text: 13800000000# 输入密码- action: find_sendby: xpathlocator: //*[@name="password"]text: 123456# 点击登录按钮- action: find_js_clickby: xpathlocator: //*[@class="inputStyle_1xJNL sumbitBtn_2QOT4"]# 验证登录成功- action: findby: xpathlocator: //*[@class="smallAvatar_2Et7V"]################### 【首页推荐位】 ########################################################################## 从首页轮播图推荐位跳转至主题详情页
from_carousel_goto_article_page:- action: find_js_clickby: xpath
#    locator: //*[@class="swiper-container swiper-container-initialized swiper-container-horizontaz"]locator: //*[@class="index-swiper "]## 从首页数据分析推荐位跳转至主题详情页
from_data_analysis_goto_article_page:- action: find_js_clickby: xpathlocator: //*[@id="__next"]/div/div/div[2]/div/div[1]/div[2]/div/div/a[3]/div################### 【首页导航】 ########################################################################## 从首页导航点击分析类栏目跳转至分析类栏目页
from_nav_goto_analysis_column_page:- action: find_hoverby: xpathlocator: //*[@id="li-9"]  # 一级导航:T台- action: find_js_clickby: xpathlocator: //div[@id="item11"]/div/div/div/a/p  # 二级导航:T台分析## 从首页导航点击图集类栏目跳转至分析类栏目页
from_nav_goto_atlas_column_page:- action: find_hoverby: xpathlocator: //*[@id="li-9"]  # 一级导航:T台- action: find_js_clickby: xpathlocator: //div[@id="item10"]/div/div/div/a/p  # 二级导航:时装发布会## 从首页导航点击图库类栏目跳转至图库类栏目页
from_nav_goto_gallery_column_page:- action: find_hoverby: xpathlocator: //*[@id="li-19"]  # 一级导航:成衣- action: find_js_clickby: xpathlocator: //div[@id="item131"]/div/div/div/a/p  # 二级导航:单品图库## 从首页导航点击DailyTrends栏目跳转至DailyTrends栏目页
from_nav_goto_daily_column_page:- action: find_hoverby: xpathlocator: //*[@id="li-195"]  # 一级导航:DailyTrends- action: find_js_clickby: xpathlocator: //div[@id="item196"]/div/div/div/a/p  # 二级导航:DailyTrends## 从首页导航点击视频栏目跳转至视频栏目页
from_nav_goto_vedio_column_page:- action: find_hoverby: xpathlocator: //*[@id="li-13"]  # 一级导航:视频- action: find_js_clickby: xpathlocator: //div[@id="item184"]/div/div/div/a/p  # 二级导航:全部视频## 从首页导航点击【图片库】跳转至综合搜索-图片库页
from_main_goto_picture_search_page:- action: find_js_clickby: xpathlocator: //*[@id="home-guide-2"]## 从首页导航点击【AI搜图】跳转至综合搜索-AI搜图页
from_main_goto_ai_page:- action: find_js_clickby: xpathlocator:## 从首页导航点击会员头像跳转至会员中心-个人中心页
from_main_goto_memger_userinfo_page:- action: find_hoverby: xpathlocator: //*[@class="loginCom_3Gl0e"]- action: find_js_clickby: xpathlocator: //*[@class="iconfont iconwode"]## 从首页导航点击关注品牌跳转至会员中心-关注品牌页
from_main_goto_memger_brandfollow_page:- action: find_hoverby: xpathlocator: //*[@class="loginCom_3Gl0e"]- action: find_js_clickby: xpathlocator: //*[@class="iconfont iconshipin_xihuan"]

datas测试数据层代码示例2:分析类文章详情页测试类 所对应的测试数据article_analysis.yaml文件

#################################### 分析类文章详情页 ########################################### 在分析类文章详情页点击切换版面
switch_page:- action: find_js_clickby: xpathlocator: //*[@id="swiper-arrow-next"]- action: find_clickby: xpathlocator: //*[@id="swiper-arrow-prev"]# 从分析类文章详情页跳转至首页
from_analysis_article_goto_main:- action: find_js_clickby: xpathlocator: //*[@class="logo_IJxdO"]# 从分析类文章详情页跳转至个人中心页
from_analysis_article_goto_userinfo_page:- action: find_hoverby: xpathlocator: //*[@id="aside-person"]- action: find_js_clickby: xpathlocator: //*[@id="main-container"]/div[5]/aside/div/div[3]/a[1]/div[1]/span

4.2.5、run测试执行层

完成了以上公共方法封装、页面对象建模、测试用例编写、测试数据准备的基础建设后,接下来就可以执行我们的测试了。
Pytest的测试用例执行主要是通过命令行的方式,前期写脚本调试的时候可以在Windows系统上执行,但我们最终是需要在测试服务器(Linux系统)上运行的。而为了实现全自动化,我们除了将测试执行的Pytest相关命令写在了run.py文件里面,同时还将测试报告的生成(通过allure插件采集和产出)、读取(无头浏览器打开检测是否有用例失败)、发送(当有失败用例时通过钉钉机器人通知)都写在了run.py文件中。

run_all_cases.py文件详细代码示例如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File :run_all_cases.py
@Auth : luoluo
@Description:
"""
import os
import time
from dingtalkchatbot.chatbot import DingtalkChatbot
from selenium import webdriver
from selenium.webdriver.chrome.options import Optionsdef run_all_cases(base_url):# 【命令行执行:杀掉因异常导致未正常关闭的 chromedriver 进程】print('【杀掉因异常导致未正常关闭的 chromedriver 进程】\n======================================')os.system("ps -ef | grep chrome | awk '{print $2}' | xargs kill -9")# 【命令行执行:pytest执行测试用例】print('【pytest执行测试用例】\n=====================================')os.system(f'pytest --base-url={base_url} ../cases/')# 【命令行执行:将测试结果转成allure测试报告】print('【将测试结果转成allure测试报告】\n======================================')os.system('/home/luoluo/allure-2.8.0/bin/allure generate ../reports/allure_results -o ../reports/allure_report --clean')# 【命令行执行:查找allure服务进程(即Java进程)并杀掉进程】print('【关闭已开启的allure服务】\n======================================')os.system("netstat -tunlp | grep java | awk '{print $7}' | awk -F \/ '{print $1}' | xargs kill -9")# 【命令行执行:重新启动allure服务】print('【重新启动allure服务】\n======================================')os.system("nohup /home/luoluo/allure-2.8.0/bin/allure serve ../reports/allure_results &")time.sleep(30)# 查找allure服务端口命令port_command = "netstat -tunlp | grep java | awk '{print $4}' | awk -F \: '{print $4}'"with os.popen(port_command, 'r') as p:port = p.read().strip()print(f'【重新启动的allure服务端口是{port}】\n=====================================')# 【无头浏览器打开allure测试报告】print('【无头浏览器打开allure测试报告】\n======================================')chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--disable-gpu')chrome_options.add_argument('--no-sandbox')driver = webdriver.Chrome(chrome_options=chrome_options)allure_report_url = f'http://192.168.1.15:{port}/index.html#behaviors'print(f'【正在打开allure测试报告地址:{allure_report_url},检查是否有用例失败】\n=====================================')driver.get(allure_report_url)driver.maximize_window()driver.implicitly_wait(5)failed_no = driver.find_element_by_xpath('//*[@class="y-label y-label_status_failed"]').textbroken_no = driver.find_element_by_xpath('//*[@class="y-label y-label_status_broken"]').text# 【检查allure测试报告是否有用例失败,有则发送钉钉机器人】print(f'失败用例数量有: {failed_no} 个,出错用例数量有: {broken_no} 个')if failed_no != '0' or broken_no != '0':# 发送通知到钉钉机器人print('报告已生成,但有异常用例,开始发送通知到钉钉机器人...')webhook = 'https://oapi.dingtalk.com/robot/send?' \'access_token=a26d714ae9d7fb89c3017b640a6a7293147adf443377dba18478daaexxxxxxxx'xiaoding = DingtalkChatbot(webhook)xiaoding.send_text(msg=f"《{base_url} UI回归测试报告》已生成\n"f"有 {failed_no} 个用例失败,有 {broken_no} 个用例出错!\n"f"请用浏览器打开查看详情\n"f"测试报告链接是:{allure_report_url}")else:print(f'《{base_url} UI回归测试报告》已生成,无异常用例,很棒!')# 【关闭浏览器】print('【关闭浏览器】\n=====================================')driver.quit()if __name__ == '__main__':base_url = 'https://www.trendtest.com/?gender_id=72105'run_all_cases(base_url)

4.2.6、logs日志层

测试日志层主要是存放在测试执行过程中捕获的日志信息,包含了通用日志和错误日志,便于运行出错时可以通过查看日志信息来定位问题。其文件内容如下截图:

4.2.7、reports测试报告层

测试报告层主要是存放在测试执行过程中采集的测试结果数据,以及失败截图,我们还需要通过allure命令来生成最终的测试报告文件,或者进行在线查看等。其目录下文件内容如下截图:

4.2.8、pytest.ini配置文件

我们还注意到该框架的根目录下有个命名为:pytest.ini的文件,它是Pytest配置文件,可以读取配置信息,改变Pytest的默认行为。要特别注意的是pytest.ini文件必须是放在项目的根目录下,且命名不能随意改变,就是固定的命名pytest.ini。

pytest.ini文件常用的参数使用分类有:
更改命令行参数addopts
自定义标记marks
自定义用例搜索规则
日志配置参数log_cli
插件配置参数

下面列举几个常用的案例:

  • 通过更改命令参数addopts可以更改Pytest的默认命令行选项,当我们执行测试时需要实现的功能较多时,比如:通过分布式去测试、失败重跑3次,测试完生成报告,如果仅仅是在cmd输入一条Pytest命令,这个命令往往是非常长串的,此时我们就可以用addopts参数代替了,从而省去重复性的敲命令工作。
  • 通过添加log_cli配置,我们可以实现控制台实时输出日志,设置日志输出详细级别。
  • 通过自定义标记注册marks,我们可以在编写测试用例时,对用例进行marks标记,比如标记为冒烟测试用例smoke,这样我们在执行测试用例时,通过执行命令:pytest -m smoke即可轻松实现只针对冒烟用例的测试。
  • 我们还可以配置常用插件参数,在本框架中用到了base_url插件,该插件可以通过控制入口base_url的输入来实现不同测试环境(比如开发环境、测试环境、正式环境等)的自由切换。

4.2.9、requirements.txt版本库文件

当前项目的所有依赖包及其精确版本号清单都会记录在此文件中,当需要在新环境部署时就可以直接拿来用了。
所以我们一般在完成详细设计后,即可生成requirements.txt 文件,生成命令是:pip freeze > requirements.txt,该文件就会存放在我们的项目根目录下。
当项目需要迁移,或者新成员需要拷贝你的项目时,首先就需要安装好项目所依赖的包,此时直接运行命令:pip install -r requirements.txt即可完成。

其文件内容如下:

requirements.txt的详细使用见下面环境部署章节。

5、环境部署

本系统我们是在Windows系统上进行脚本的编写和调试,但最终的执行环境是在Linux系统的测试服务器上。为了方便项目的迁移和提高环境部署的效率,我们在Windows系统上进行脚本的编写和调试时使用的是Python虚拟环境,与Python全局环境独立开来,我们只需要在这个虚拟环境中安装当前项目的所有依赖包,统一通过requirements.txt 文件来进行管理。
同样的,既然项目需要从Windows系统迁移到Linux系统,我们也需要对项目的代码进行管理,代码的提交、拉取等同步工具我们选取了国内流行的gitee。

下面是环境部署的具体操作步骤:
前提条件:我们的Linux测试服务器上已经安装了jdk1.8、python3.8、allure、谷歌浏览器驱动chromedriver、Git工具,并配置好了环境变量。

  1. 在Windows的Pycham上使用虚拟环境,安装好项目工程需要的各种依赖包
  2. 在Windows上的Pycham上先将项目工程的依赖包批量导出到requirements.txt文件中:pip freeze > requirements.txt,然后commit和push提交到远程仓库
  3. 到Linux环境的工程根目录下,创建虚拟环境到venv目录(即命令中第二个venv,如果没有则自动创建):python3.8 -m venv venv
  4. 查看虚拟环境:cd venv/
  5. 进入虚拟环境bin/目录下
  6. 激活虚拟环境:source activate
  7. 回到工程根目录:git pull(如果本地文件有冲突用命令:1,git fetch --all 2,git reset --hard origin/master)
  8. pip批量安装依赖包:pip install -r requirements.txt(前后用pip list查看是否安装完成)
  9. 进入run目录:cd run
  10. 执行run文件:python run_all_cases.py

6、持续测试

通过以上环节,我们已经成功的将系统在Linux测试服务器上运行起来了,但我们注意到的一个问题是:我们仍需人工进入到Linux测试服务器上去操作。随着项目的频繁迭代,回归或冒烟测试的次数也会越来越频繁,人工构建时效率慢的缺点也暴露无遗。
为了能够快速的构建自动化测试,我们还需要将自动化测试与Jenkins工具结合起来。那如何将Jenkins与我们的自动化测试项目结合起来呢?

前提条件:我们已经安装部署好了Jenkins
操作步骤:

  1. 新建任务:在Jenkins上我们新建一个自由风格的任务
  2. 配置任务:我们可以配置成自动触发构建,或者定时构建,构建的shell脚本如下:
    sudo su -
    cd /data/wwwroot/auto_test/UI_auto_test/
    git pull
    source venv/bin/activate
    cd run
    python run_all_cases.py

至此,我们即完成了自动化测试系统与Jenkins集成,从而实现了自动构建。

7、系统调优

经过系统的架构设计、详细设计、部署测试后,系统本身会遇到很多问题,是需要不断的调试和优化的,接下来我们将列举几个遇到的问题和对应的调优方案。

7.1、系统运行不稳定问题

【问题描述】:运行自动化测试脚本总是出现不稳定的情况,这次用例正常通过,下次用例又出现失败等。
【解决方案】:
第一,从基本的工具使用层面考虑:

  • 使用Selenium自带的容错功能,比如Selenium的三种等待方式的合理应用;元素定位方式最优选(尽量选取不会轻易变动的元素属性定位)。
  • 还可以使用 Pytest自带的容错功能,Pytest的失败用例重跑机制:只需要在安装pytest-rerunfailures插件,然后在执行pytest命令行时,增加参数:–reruns 3 --reruns-delay 2 即可。
  • 测试断言可以使用pytest-assume 多重较验方法,即将常规的assert 断言方法替换为:pytest.assume(断言表达式)。为什么要用assume呢?常规的assert 断言,一个方法中写多条断言,中间有 1 条失败,后面的代码就不执行了,而我们希望有失败也能执行完毕。

第二,从框架设计层面考虑:

  • 测试用例设计尽量做到独立无依赖,避免某个依赖的用例失败,当前用例也无法正常执行的情况发生
  • 使用try except finally异常处理和添加日志,发现出错可以更快速的进行问题追溯

7.2、系统执行时间过长问题

【问题描述】:我们做自动化测试的初衷是希望可以通过自动化脚本来替代人工去做重复的费时的回归测试,或者在发版时可以快速的执行完冒烟测试,但是也遇到了自动化测试执行时间过长的问题。
【解决方案】:增加按文件名进行分布式执行机制(并发)。
要想实现并发,我们就得引入多进程,目前pytest框架中有个pytest-xdist插件是支持进程级别的并发的。
具体操作步骤是:安装pytest-xdist插件,在pytest.ini文件的addopts中传命令行参数:-n auto --dist=loadfile。其中-n后面的参数表示cpu的数量,也可以指定为auto,它会自动检测cpu核数并发运行用例。
非常值得注意的是:分布式执行用例的三个设计原则

  • 用例之间是独立且没有依赖关系的,即单独拎出来也是可以完全独立运行的。
  • 用例执行不要有顺序,即随机顺序都能正常执行的。
  • 每个用例都能重复运行,其他用例不会受其运行结果的影响。

7.3、系统多环境切换问题

【问题描述】:我们希望可以实现多环境自由切换测试(测试环境、预发布环境、正式环境等)
【解决方案】:增加环境base_url自由切换机制。
我们只需要在执行测试时,在pytest的命令行传入对应环境的base_url即可。具体操作步骤如下:

  1. 安装pytest-base-url插件
  2. 在conftest.py的fixture函数init_driver中传入base_url参数
  3. 在执行pytest命令行时传命令行参数:–base-url={base_url}

8、系统评价

本系统着眼于当前软件研发流程中自动化软件测试环节,详细剖析了Web UI自动化测试系统的开发、调试、部署、运行、调优过程,逐步的实现和完善了该系统所需要满足的功能需求和稳定性需求,并在实际项目中经过了严格的实践应用。系统技术栈:Python+Selenium+Pytest+Allure+Gitee+Jenkins 都是当下最为主流的,同时该系统因其具有复用性、容错性、可移植性等优点,也得到了广泛的认同,本系统也将逐步改进优化,相信它会得到很好的应用。

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

  1. Web UI 自动化测试框架 seldom

    基于 selenium 和 unittest 的 Web UI自动化测试框架. 特点 提供更加简单API编写自动化测试. 提供脚手架,快速生成自动化测试项目. 自动生成HTML测试报告生成. 自带断言 ...

  2. WEB UI自动化测试框架搭建(一)_公用方法Utils

    本栏目内的所有项目使用的都是PyCharm 2020.1专业版,可以下载后自行在网上找教程破解. WEB UI自动化测试框架搭建(一)~(七)源代码:https://download.csdn.net ...

  3. Python+Behave+Allure Web UI自动化测试

    基于BDD模式的Web UI自动化测试体验,集成了python,behave,allure,非主流的一个路线,可以一起玩玩. 1. 概念解释 Python: 大家都懂,3以上版本 Behave:行为驱 ...

  4. Web UI自动化测试系统设计

    Web UI自动化测试系统设计 1. 自动化测试简介 1.1 什么是好的自动化测试框架 在创建测试自动化框架时,我们应该考虑以下要点: 能够通过使用适当的抽象层快速创建自动化测试 应易于维护和扩展应该 ...

  5. 种草Cypress和TestCafe,QA同学一定想了解的Web UI自动化测试工具

    摘要:Cypress和TestCafe这两个工具相比于Selenium都更加的轻量级,且在不同的方面有了改进,比如安装更简单,增加了内置等待机制,调试更加方便等. Cypress.TestCafe.P ...

  6. 博客后台和首页php,基于ThinkPHP5.1+Bootstrap框架开发的博客系统和后台权限管理系统...

    源码介绍 基于ThinkPHP5.1+Bootstrap框架开发的博客系统和后台开发框架,主要是基于最新的inkPHP5.1.9框架作为底层开发核心,前端采用Bootstrap框架实现界面响应式设计, ...

  7. 通过录屏自动测试web端查询功能_海豚 Web UI自动化测试工具

    http://div.io/topic/1339 海豚是什么? 没错,它是一个 Web UI自动化测试工具,专治Web UI的各种疑难杂症,目前只针对移动端Webapp量身定做. 说到 Web UI的 ...

  8. Web UI自动化测试实例(登录网易云音乐账户)——(五)Taiko

    初步想法:用Taiko自动化工具,实现登录太简单了吧! 一.对taiko的认识 初识taiko: 自动化测试工具-----Taiko taiko的安装:可能踩如下坑,那些年的坑之node.js 和 t ...

  9. Django框架下图书馆座位预约系统课程设计

    Django框架下图书馆座位预约系统课程设计 1.登录注册界面 2.首页轮播图 3.座位预约列表 4.实现预约功能 5.管理座位,增删改查 6.管理用户,增删改查 7.用户充值,预约座位要花钱 相关代 ...

最新文章

  1. opencv学习笔记(二)
  2. linux下yum包更新不了
  3. keras保存模型_onnx+tensorrt部署keras模型
  4. WCF服务寄宿IIS时.SVC文件无法浏览的解决办法
  5. jQuery 学习笔记(三)——事件与应用
  6. qt中拖动窗口widget
  7. MySQL的基本命令
  8. Home键的获取监听,安卓4.0后就不能在onkeydown方法中获取了。怎么办。
  9. jquery替换节点
  10. 【sklearn第二十二讲】协方差估计
  11. 中文金融情感词典发布啦 | 附代码
  12. 用Quartus仿真计数器模块:
  13. 【对抗攻击代码实战】对抗样本的生成——FGSM
  14. 如果你想专升本那就一定要看的专升本语文_文学常识完整版(四)
  15. 建网站购买服务器,建网站购买服务器
  16. Win快捷键大全史上最全~ win打开资源管理器快捷键(查看内存的占用)
  17. Goldendict 1.5.0 VS2015 Qt 5.7 源代码编译
  18. js 字符串转二维数组
  19. SQL2005导入数据至2000的问题
  20. PostGIS教程十三:地理

热门文章

  1. 蹦起来!Python 用物理引擎Pymunk写一个解压小游戏
  2. 毕业设计-基于机器视觉的斑马线分割- Unet
  3. Android IoT开发实战 | 01 - Android 开发利器 Android Studio 3.5.3
  4. Java 手写签字去除背景 背景透明
  5. 追忆计算机之父艾伦图灵:在一个时刻两次改变历史的人
  6. 【赵强老师】事务隔离级别与脏读
  7. 最近找实习的笔试面试经历
  8. c/c++ 32/64位兼容编程
  9. EFI和UEFI的区别
  10. 一文带你了解开源数据库中的佼佼者 TOP 10