Web自动化测试

a910cf5229d24631a0f4d29b2e43a97e.png


课程目标

1.掌握使用Selenium进行Web自动化测试的流程和方法,并且能够完成自动化测试脚本的编写。
2.掌握如何通过UnitTest管理用例脚本,并生成HTML测试报告。
3.掌握使用PO模式来设计自动化测试代码的架构。
4.掌握使用数据驱动来实现自动化测试代码和测试数据的分离。
5.掌握使用logging来实现日志的收集。

Web自动化入门

目标:

1. 理解自动化测试的相关概念
2. 了解Selenium的特点
3. 掌握如何搭建web自动化测试的相关环境
4. 熟练掌握web自动化测试脚本编写的基本步骤

一、Web自动化测试

目标:

1. 了解什么是自动化
2. 理解什么是自动化测试
3. 知道自动化测试能解决什么问题
4. 理解什么样的Web项目适合自动化测试

什么是自动化

概念:由机器设备代替人工自动完成指定目标的过程.


优点

1. 减少人工劳动力
2. 提高工作效率
3. 产品规格统一标准
4. 规模化(批量生产)

什么是自动化测试

概念:让程序代替人工去验证系统功能的过程


自动化测试能解决什么问题?

1. 解决-回归测试
2. 解决-压力测试
3. 解决-兼容性测试
4. 提高测试效率,保证产品质量
回归测试:项目在发新版本之后对项目之前的功能进行验证
压力测试:可以理解多用户同时去操作软件,统计软件服务器处理多用户请求的能力
兼容性测试:不同浏览器(IE、Firefox、Chrome)等等

自动化测试相关知识

优点

1. 较少的时间内运行更多的测试用例;
2. 自动化脚本可重复运行;
3. 减少人为的错误;
4. 克服手工测试的局限性;

误区

1. 自动化测试可以完全替代手工测试;
2. 自动化测试一定比手工测试厉害;
3. 自动化测试可以发掘更多的BUG;
4. 自动化测试适用于所有功能;

自动化测试分类

1. Web-自动化测试(本阶段学习)
2. 移动-自动化测试
3. 接口-自动化测试
4. 单元测试-自动化测试

什么是Web自动化测试

概念:让程序 代替人工自动验证 Web项目功能的过程


什么Web项目适合做自动化测试?

1.需求变动不频繁
2.项目周期长
3.项目需要回归测试

Web自动化测试在什么阶段开始?

功能测试完毕(手工测试)

总结:

1. 自动化测试的概念?
2. 自动化测试能解决什么问题?
3. 什么样的Web项目适合自动化测试?
4. Web自动化测试所属分类?

二、Web自动化测试工具选择

目标

1. 了解Web自动化测试常用工具
2. 熟悉Selenium的特点

主流的Web自动化测试工具

1. QTP
    QTP是一个商业化的功能测试工具,收费,支持web,桌面自动化测试。
2. Selenium(本阶段学习)
    Selenium是一个开源的web自动化测试工具,免费,主要做功能测试。
3. Robot framework
    Robot Framework是一个基于Python可扩展地关键字驱动的测试自动化框架。

什么是Selenium

1. 开源软件:源代码开放可以根据需要来增加工具的某些功能
2. 跨平台:linux、windows、mac
3. 支持多种浏览器:Firefox、Chrome、IE、Edge、Opera、Safari等
4. 支持多种语言:Python、Java、C#、JavaScript、Ruby、PHP等
5. 成熟稳定:目前已经被google、百度、腾讯等公司广泛使用
6. 功能强大:能够实现类似商业工具的大部分功能,因为开源性,可实现定制化功能

 

Selenium是一个用于Web应用程序的自动化测试工具;中文的意思(硒)


Selenium特点

三、环境搭建

目标

1. 掌握如何搭建web自动化测试的相关环境
2. 熟练掌握web自动化测试脚本编写的基本步骤

环境搭建

基于Python环境搭建

1. Python 开发环境
2. 安装selenium包
3. 安装浏览器
4. 安装浏览器驱动 -- 保证能够用程序驱动浏览器,实现自动化测试

安装selenium

前提:Python3 安装完毕且能正常运行

PIP工具

pip是一个通用的 Python 包管理工具,提供了对 Python 包的查找、下载、安装、卸载的功能。

安装

pip install selenium

卸载

pip uninstall selenium

查看

pip show selenium

四、入门示例

需求:

通过程序启动浏览器,并打开百度首页,暂停3秒,关闭浏览器

实现步骤

1. 导包
  from selenium import webdriver
2. 创建浏览器驱动对象
  Firefox浏览器:driver = webdriver.Firefox()
  Chrome浏览器:driver = webdriver.Chrome()
  Edge浏览器:driver = webdriver.Edge()
3. 打开Web页面
  driver.get("http://www.baidu.com/")
4. 暂停
  time.sleep(3)
5. 关闭驱动对象
  driver.quit()

示例代码

# 导包
  from selenium import webdriver
  import time
# 创建浏览器驱动对象
  driver = webdriver.Firefox()
  # driver = webdriver.Chrome()
  # driver = webdriver.Edge()
# 加载web页面
  driver.get("http://www.baidu.com/")
# 暂停3秒
  time.sleep(3)
# 关闭驱动对象
  driver.quit()

Web自动化测试所属分类

1. 黑盒测试(功能测试)
2. 白盒测试(单元测试)
3. 灰盒测试(接口测试)
Web自动化测试属于黑盒测试(功能测试)

Selenium-API操作

目标:

1. 熟练应用八种元素定位方式
2. 掌握对元素和浏览器的操作方法
3. 掌握键盘鼠标的操作
4. 掌握元素等待的操作
5. 掌握下拉选择框、警告框和滚动条的操作
6. 掌握如何切换frame框架和多窗口
7. 掌握如何实现窗口截图

元素定位

1. 掌握id、name、class_name、tag_name、link_text、partial_link_text定位方式的使用

思考:为什么要学习元素定位?

让程序操作指定元素,就必须先找到此元素。

如何进行元素定位?

html页面由标签构成,标签的基本格式如下:
<标签名 属性名1="属性值1" 属性名2="属性值2">文本</标签名>
示例:
<input id="username" type="text" name="username" placeholder="用户名" />
<div id="my_cart">
<span>我的购物车</span>
</div>

思考:如何快速的查看一个元素的相关信息?

浏览器开发者工具就是给专业的web应用和网站开发人员使用的工具。 包含了对HTML查看和编辑、Javascript控制台、网络状况监视等功能,是开发JavaScript、CSS、HTML和Ajax的得力助手。


作用:快速定位元素,查看元素信息


如何使用浏览器开发者工具

安装:

浏览器已默认安装,可以直接使用。


启动:

  • 快捷键:一般在windows系统上打开浏览器开发者工具都是按F12

  • 火狐浏览器:在页面上点击右键选择'查看元素'

  • 谷歌浏览器:在页面上点击右键选择‘检查’


使用:

  • 方法一:在要查看的元素上点击右键选择‘查看元素’或者‘检查’

  • 方法二:先打开浏览器开发者工具,点击选择元素的图标,移动鼠标到要查看的元素,然后点击


一、元素定位

Selenium提供了八种定位元素方式
1. id
2. name
3. class_name
4. tag_name
5. link_text
6. partial_link_text
7. XPath
8. CSS

定位

说明:id定位就是通过元素的id属性来定位元素,
      HTML规定id属性在整个HTML文档中必须是唯一的;
前提:元素有id属性

id定位方法

element = driver.find_element_by_id(id)

案例

案例演示环境说明:
受限于网络速度的影响,我们案例采用本地的html页面来演示。这样可以提高学习效率和脚本执行速率。
需求:打开注册A.html页面,完成以下操作
1).使用id定位,输入用户名:admin
2).使用id定位,输入密码:123456
3).3秒后关闭浏览器窗口

案例实现步骤分析

1. 导入selenium包 --> from selenium import webdriver
2. 导入time包 --> import time
3. 实例化浏览器驱动对象 --> driver = webdriver.Firefox()
4. 打开注册A.html --> driver.get(url)
5. 调用id定位方法 --> element = driver.find_element_by_id("")
6. 使用send_keys()方法输入内容 --> element.send_keys("admin")
7. 暂停3秒 --> time.sleep(3)
8. 关闭浏览器驱动对象 --> driver.quit()
说明:为了更好的学习体验,我们先暂时使用下send_keys()方法来输入内容

name 定位

说明:name定位就是根据元素name属性来定位。HTML文档中name的属性值是可以重复的。
前提:元素有name属性
​
element = driver.find_element_by_name(name)

案例

需求:打开注册A.html页面,完成以下操作
1).使用name定位用户名,输入:admin
2).使用name定位密码,输入:123456
3).3秒后关闭浏览器窗口

class_name定位

说明:class_name定位就是根据元素class属性值来定位元素。HTML通过使用class来定义元素的样式。
前提:元素有class属性
注意:如果class有多个属性值,只能使用其中的一个

class_name定位方法

element = driver.find_element_by_class_name(class_name}

案例

需求:打开注册A.html页面,完成以下操作
1).通过class_name定位电话号码A,并输入:18611111111
2).通过class_name定位电子邮箱A,并输入:123@qq.com
3).3秒后关闭浏览器窗口

tag_name定位

说明:
    tag_name定位就是通过标签名来定位;
    HTML本质就是由不同的tag组成,每一种标签一般在页面中会存在多个,
    所以不方便进行精确定位,一般很少使用

tag_name定位方法

element = driver.find_element_by_tag_name(tag_name)
如果存在多个相同标签,则返回符合条件的第一个标签
如何获取第二个元素?稍后讲解

案例

需求:打开注册A.html页面,完成以下操作
1).使用tag_name定位用户名输入框,并输入:admin
2).3秒后关闭浏览器窗口

link_text定位

说明:link_text定位是专门用来定位超链接元素(<a>标签</a>),并且是通过超链接的文本内容来定位元素

link_text定位方法

element = driver.find_element_by_link_text(link_text)
link_text:为超链接的全部文本内容

案例

需求:打开注册A.html页面,完成以下操作
1).使用link_text定位(访问 新浪 网站)超链接,并点击
2).3秒后关闭浏览器窗口

partial_link_text定位

说明:
    partial_link_text定位是对link_text定位的补充,
    link_text使用全部文本内容匹配元素,
    而partial_link_text可以使用局部来匹配元素,
    也可以使用全部文本内容匹配元素。

partial_link_text定位方法

element = driver.find_element_by_partial_link_text(partial_link_text)
partial_link_text:可以传入a标签局部文本-能表达唯一性

案例

需求:打开注册A.html页面,完成以下操作
1).使用partial_link_text定位(访问 新浪 网站)超链接,并点击
2).3秒后关闭浏览器窗口

定位一组元素

在我们学习使用以上方法的时候,发现有个共同的相似方法:

find_elements_by_xxx()

作用:
1). 查找定位所有符合条件的元素
2). 返回的定位元素格式为数组(列表)格式;
说明:
3). 列表数据格式的读取需要指定下标(下标从0开始)

案例

需求:打开注册A.html页面,完成以下操作
1).使用tag_name定位密码输入框(第二个input标签),并输入:123456
2).3秒后关闭浏览器窗口

示例代码

driver.find_elements_by_tag_name("input")[1].send_keys("123456")

二、XPath定位

目标

1. 掌握XPath定位策略
2. 掌握CSS定位策略

为什么要学习XPathCSS定位?

1. 如果要定位的元素没有id、name、class属性,该如何进行定位?
2. 如果通过name、class、tag_name无法定位到唯一的元素,该如何进行定位?
示例:
<input type="submit" value="提交" />

什么是XPath?

1. XPath即为XML Path的简称,它是一门在 XML 文档中查找元素信息的语言。
2. HTML可以看做是XML的一种实现,所以Selenium用户可以使用这种强大的语言在Web应用中定位元素。
XML:一种标记语言,用于数据的存储和传递。 后缀.xml结尾

<?xml version="1.0" encoding="UTF-8" ?>
<node>
<db id="db" desc="三条边的长度都一样">
<b1>3</b1>
<b2>3</b2>
<b3>3</b3>
<expect>等边三角形</expect>
</db>
<dy>
<b1>4</b1>
<b2>4</b2>
<b3>5</b3>
<expect>等腰三角形</expect>
</dy>
</node>

XPath定位方式之所以强大,是因为它有非常灵活的定位策略


XPath定位策略(方式)

1. 路径-定位
2. 利用元素属性-定位
3. 属性与逻辑结合-定位
4. 层级与属性结合-定位

XPath定位方法

element = driver.find_element_by_xpath(xpath)

路径定位(绝对路径、相对路径)

绝对路径:从最外层元素到指定元素之间所有经过元素层级的路径

1). 绝对路径以/html根节点开始,使用/来分隔元素层级;
如:/html/body/div/fieldset/p[1]/input
2). 绝对路径对页面结构要求比较严格,不建议使用

相对路径:匹配任意层级的元素,不限制元素的位置


1). 相对路径以//开始

2). 格式://input 或者 //*

练习

需求:打开注册A.html页面,完成以下操作
1).使用绝对路径定位用户名输入框,并输入:admin
2).暂停2秒
3).使用相对路径定位用户名输入框,并输入:123

利用元素属性

说明:通过使用元素的属性信息来定位元素
格式://input[@id='userA'] 或者 //*[@id='userA']

属性与逻辑结合

说明:解决元素之间个相同属性重名问题
格式://*[@name='tel' and @class='tel']

层级与属性结合

说明:如果通过元素自身的信息不方便直接定位到该元素,则可以先定位到其父级元素,然后再找到该元素格式://*[@id='p1']/input

XPath-延伸

//*[text()="xxx"] 文本内容是xxx的元素
//*[contains(@attribute,'xxx')] 属性中含有xxx的元素
//*[starts-with(@attribute,'xxx')] 属性以xxx开头的元素

XPath-总结

XPath定位策略有哪些?

三、CSS定位

什么是CSS定位?

1. CSS(Cascading Style Sheets)是一种语言,它用来描述HTML元素的显示样式;
2. 在CSS中,选择器是一种模式,用于选择需要添加样式的元素;
3. 在Selenium中也可以使用这种选择器来定位元素。
提示:
1. 在selenium中推荐使用CSS定位,因为它比XPath定位速度要快
2. css选择器语法非常强大,在这里我们只学习在测试中常用的几个

CSS定位方法

element = driver.find_element_by_css_selector(css_selector)

CSS定位常用策略 (方式)

1. id选择器
2. class选择器
3. 元素选择器
4. 属性选择器
5. 层级选择器

id选择器

说明:根据元素id属性来选择
格式:#id
例如:#userA <选择id属性值为userA的元素>

class选择器

说明:根据元素class属性来选择
格式:.class
例如:.telA <选择class属性值为telA的所有元素>

元素选择器

说明:根据元素的标签名选择
格式:element
例如:input <选择所有input元素>

属性选择器

说明:根据元素的属性名和值来选择
格式:[attribute=value] element[attribute=value]
例如:[type="password"] <选择type属性值为password的元素>

层级选择器

说明:根据元素的父子关系来选择
格式1:element1>element2 通过element1来定位element2,并且element2必须为element1的直接子
元素
例如1:p[id='p1']>input <定位指定p元素下的直接子元素input>
格式2:element1 element2 通过element1来定位element2,并且element2为element1的后代元素
例如2:p[id='p1'] input <定位指定p元素下的后代元素input>

CSS延伸[了解]

input[type^='p'] type属性以p字母开头的元素
input[type$='d'] type属性以d字母结束的元素
input[type*='w'] type属性包含w字母的元素

CSS总结

常用的CSS定位选择器有哪些?

元素定位—汇总

1. id、name、class_name:为元素属性定位
2. tag_name:为元素标签名称
3. link_text、partial_link_text:为超链接定位(a标签)
4. XPath:为元素路径定位
5. CSS:为CSS选择器定位

XPathCSS类似功能对比


另一种写法(了解)

方法:find_element(by=By.ID, value=None)
备注:需要两个参数,第一个参数为定位的类型由By提供,第二个参数为定位的具体方式
示例:
1. driver.find_element(By.CSS_SELECTOR, '#emailA').send_keys("123@126.com")
2. driver.find_element(By.XPATH, '//*[@id="emailA"]').send_keys('234@qq.com')
3. driver.find_element(By.ID, "userA").send_keys("admin")
4. driver.find_element(By.NAME, "passwordA").send_keys("123456")
5. driver.find_element(By.CLASS_NAME, "telA").send_keys("18611111111")
6. driver.find_element(By.TAG_NAME, 'input').send_keys("123")
7. driver.find_element(By.LINK_TEXT, '访问 新浪 网站').click()
8. driver.find_element(By.PARTIAL_LINK_TEXT, '访问').click()

导包:from selenium.webdriver.common.by import By

class By(object):
"""
Set of supported locator strategies.
"""
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"

find_element_by_xxx()find_element() 区别

说明:通过查看find_element_by_id底层实现方法,发现底层是调用find_element方法进行的封装;

def find_element_by_id(self, id_):
"""Finds an element by id.
:Args:
- id\_ - The id of the element to be found.
:Usage:
driver.find_element_by_id('foo')
"""
return self.find_element(by=By.ID, value=id_)

六、元素操作方法

目标

1. 掌握常用的元素操作方法
2. 掌握常用的操作浏览器方法
3. 知道常用的获取元素信息的方法

元素操作

为什么要学习操作元素的方法?

1. 需要让脚本模拟用户给指定元素输入值
2. 需要让脚本模拟人为删除元素的内容
3. 需要让脚本模拟点击操作

元素常用操作方法

1. click() 单击元素
2. send_keys(value) 模拟输入
3. clear() 清除文本

案例

需求:打开注册A页面,完成以下操作
1).通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:123@qq.com
2).间隔3秒,修改电话号码为:18600000000
3).间隔3秒,点击‘注册’按钮
4).间隔3秒,关闭浏览器
5).元素定位方法不限

实现步骤难点分析

1. 修改电话号码,先清除再输入新的号码; 清除 --> clear()
2. 点击按钮 --> click()

浏览器操作

脚本启动浏览器窗口大小默认不是全屏?
如何刷新页面?

答案:通过调用Selenium的API来实现浏览器的操作


七、浏览器操作方法

1. maximize_window() 最大化浏览器窗口 --> 模拟浏览器最大化按钮
2. set_window_size(width, height) 设置浏览器窗口大小 --> 设置浏览器宽、高(像素点)
3. set_window_position(x, y) 设置浏览器窗口位置 --> 设置浏览器位置
4. back() 后退 --> 模拟浏览器后退按钮
5. forward() 前进 --> 模拟浏览器前进按钮
6. refresh() 刷新 --> 模拟浏览器F5刷新
7. close() 关闭当前窗口 --> 模拟点击浏览器关闭按钮
8. quit() 关闭浏览器驱动对象 --> 关闭所有程序启动的窗口
9. title 获取页面title
10. current_url 获取当前页面URL

示例代码:

# 最大化浏览器
driver.maximize_window()
# 刷新
driver.refresh()
# 后退
driver.back()
# 前进
driver.forward()
# 设置浏览器大小
driver.set_window_size(300,300)
# 设置浏览器位置
driver.set_window_position(300,200)
# 关闭浏览器单个窗口
driver.close()
# 关闭浏览器所有窗口
driver.quit()
# 获取title
title = driver.title
# 获取当前页面url
url = driver.current_url

获取元素信息

为什么要学习获取元素信息的方法?

1. 如何获取元素的文本?
2. 如何获取元素属性值?
3. 如何让程序判断元素是否为可见状态?
我们想解决以上问题,就需要学习Selenium封装的获取元素信息的方法

获取元素信息的常用方法

1. size 返回元素大小
2. text 获取元素的文本
3. get_attribute("xxx") 获取属性值,传递的参数为元素的属性名
4. is_displayed() 判断元素是否可见
5. is_enabled() 判断元素是否可用
6. is_selected() 判断元素是否选中,用来检查复选框或单选按钮是否被选中
提示:
1. size、text:为属性,调用时无括号;如:xxx.size

案例

需求:使用‘注册A.html’页面,完成以下操作:
1).获取用户名输入框的大小
2).获取页面上第一个超链接的文本内容
3).获取页面上第一个超链接的地址
4).判断页面中的span标签是否可见
5).判断页面中取消按钮是否可用
6).判断页面中'旅游'对应的复选框是否为选中的状态

总结

1. 常用的元素操作方法?
2. 常用的操作浏览器方法?
3. 常用的获取元素信息的方法?

八、鼠标操作

目标

1. 掌握鼠标操作的方法
2. 掌握键盘操作的方法

鼠标操作

常见的鼠标操作有:点击、右击、双击、悬停、拖拽等,对于这些鼠标操作Selenium都封装了相应的操作方法。


为什么要操作鼠标?

现在Web产品中存在丰富的鼠标交互方式,作为一个Web自动化测试框架,需要应对这些鼠标操作的应用场景。


鼠标操作的方法

说明:在Selenium中将操作鼠标的方法封装在ActionChains类中
实例化对象:
action = ActionChains(driver)
方法:
1. context_click(element) 右击 --> 模拟鼠标右键点击效果
2. double_click(element) 双击 --> 模拟鼠标双击效果
3. drag_and_drop(source, target) 拖动 --> 模拟鼠标拖动效果
4. move_to_element(element) 悬停 --> 模拟鼠标悬停效果
5. perform() 执行 --> 此方法用来执行以上所有鼠标操作
为了更好的学习其他方法,我们先学习perform()执行方法,因为所有的方法都需要执行才能生效

鼠标执行-perform()

说明:在ActionChains类中所有提供的鼠标事件方法,在调用的时候所有的行为都存储在ActionChains对
象中,
而perform()方法就是真正去执行所有的鼠标事件。
强调:必须调用perform()方法才能执行鼠标事件

鼠标右键-context_click()

说明:对于点击鼠标右键,如果弹出的是浏览器默认的菜单,Selenium没有提供操作菜单选项的方法;
如果是自定义的右键菜单,则可以通过元素定位来操作菜单中的选项。

代码实现关键点分析

1. 导包:from selenium.webdriver.common.action_chains import ActionChains
2. 实例化ActionChains对象:action = ActionChains(driver)
3. 调用右键方法:action.context_click(element)
4. 执行:action.perform()

鼠标双击-double_click()

说明:模拟双击鼠标左键操作

鼠标拖动-drag_and_drop()

说明:模拟鼠标拖动动作,选定拖动源元素释放到目标元素


拖动关键点分析

1. 源元素 source = driver.find_element_by_id(xxx)
2. 目标元素 target = driver.find_element_by_id(xxx)
3. 调用方法 action.drag_and_drop(source, target).perform()

鼠标悬停-move_to_element()

说明: 模拟鼠标悬停在指定的的元素上

鼠标操作总结

1. 鼠标右击
2. 鼠标双击
3. 鼠标拖拽
4. 鼠标悬停
5. 鼠标执行

九、键盘操作

思考:如何实现复制、粘贴的操作?

说明:
1). 模拟键盘上一些按键或者组合键的输入 如:Ctrl+C 、Ctrl+V;
2). Selenium中把键盘的按键都封装在Keys类中

Keys

导包:from selenium.webdriver.common.keys import Keys

常用的键盘操作

1. send_keys(Keys.BACK_SPACE) 删除键(BackSpace)
2. send_keys(Keys.SPACE) 空格键(Space)
3. send_keys(Keys.TAB) 制表键(Tab)
4. send_keys(Keys.ESCAPE) 回退键(Esc)
5. send_keys(Keys.ENTER) 回车键(Enter)
6. send_keys(Keys.CONTROL,'a') 全选(Ctrl+A)
7. send_keys(Keys.CONTROL,'c') 复制(Ctrl+C)
提示:以上方法就不一个一个讲解了,因为调用方法都一样;

案例

需求:打开注册A页面,完成以下操作
1). 输入用户名:admin1,暂停2秒,删除1
2). 全选用户名:admin,暂停2秒
3). 复制用户名:admin,暂停2秒
4). 粘贴到密码框

示例代码

# 定位用户名
element = driver.find_element_by_id("userA")
# 输入用户名
element.send_keys("admin1")
# 删除1
element.send_keys(Keys.BACK_SPACE)
# 全选
element.send_keys(Keys.CONTROL, 'a')
# 复制
element.send_keys(Keys.CONTROL, 'c')
# 粘贴
driver.find_element_by_id('passwordA').send_keys(Keys.CONTROL, 'v')

十、元素等待

目标:

1. 掌握元素的隐式等待
2. 掌握元素的显式等待

什么是元素等待?

概念:在定位页面元素时如果未找到,会在指定时间内一直等待的过程。


为什么要设置元素等待?

1. 网络速度慢
2. 电脑配置低
3. 服务器处理请求慢
Selenium中元素等待有几种类型呢?

元素等待类型

1. 隐式等待
2. 显式等待

隐式等待

概念:定位元素时,如果能定位到元素则直接返回该元素,不触发等待; 如果不能定位到该元素,则间隔一段时间后再去定位元素; 如果在达到最大时长时还没有找到指定元素,则抛出元素不存在的异常NoSuchElementException 。


实现方式

方法:driver.implicitly_wait(timeout)
(timeout:为等待最大时长,单位:秒)
说明:隐式等待为全局设置(只需要设置一次,就会作用于所有元素)

显式等待

概念:定位指定元素时,如果能定位到元素则直接返回该元素,不触发等待; 如果不能定位到该元素,则间隔一段时间后再去定位元素; 如果在达到最大时长时还没有找到指定元素,则抛出超时异常 TimeoutException 。


在Selenium中把显式等待的相关方法封装在WebDriverWait类中。


实现方式

2. WebDriverWait(driver, timeout, poll_frequency=0.5)
1). driver:浏览器驱动对象
2). timeout:超时的时长,单位:秒
3). poll_frequency:检测间隔时间,默认为0.5秒
3. 调用方法 until(method):直到...时
1). method:函数名称,该函数用来实现对元素的定位
2). 一般使用匿名函数来实现:lambda x: x.find_element_by_id("userA")
4. element = WebDriverWait(driver, 10, 1).until(lambda x: x.find_element_by_id("userA"))

示例代码

import time
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Firefox()
driver.get("file:///D:/webAutoTest/page/注册A.html")
element = WebDriverWait(driver, 10, 1).until(lambda x: x.find_element_by_id("userA"))
element.send_keys("admin")
time.sleep(3)
driver.quit()

显式与隐式区别

1. 作用域:隐式为全局元素,显式等待为单个元素有效
2. 使用方法:隐式等待直接通过驱动对象调用,而显式等待方法封装在WebDriverWait类中
3. 达到最大超时时长后抛出的异常不同:隐式为NoSuchElementException,显式等待为TimeoutException

十一、下拉选择框

目标

1. 掌握下拉选择框的操作方法
2. 掌握处理弹出框的方法
3. 掌握调用JavaScript方法

如何操作下拉选择框?

案例

需求:使用‘注册A.html’页面,完成对城市的下拉框的操作
1).选择‘广州’
2).暂停2秒,选择‘上海’
3).暂停2秒,选择‘北京’

案例实现方式一

思路:先定位到要操作的option元素,然后执行点击操作

问题

操作起来比较繁琐:要先定位到要操作的选项,然后再执行点击操作

Select

说明:Select类是Selenium为操作select标签特殊封装的。
实例化对象:
select = Select(element)
element: <select>标签对应的元素,通过元素定位方式获取,
例如:driver.find_element_by_id("selectA")

操作方法:
1. select_by_index(index) --> 根据option索引来定位,从0开始
2. select_by_value(value) --> 根据option属性 value值来定位
3. select_by_visible_text(text) --> 根据option显示文本来定位

Select类实现步骤分析

1. 导包 Select类 --> from selenium.webdriver.support.select import Select
2. 实例化Select类 select = Select(driver.find_element_by_id("selectA"))
3. 调用方法:select.select_by_index(index)

示例代码

#导包
from selenium.webdriver.support.select import Select
select = Select(driver.find_element_by_id("selectA"))
select.select_by_index(2) # 根据索引实现
select.select_by_value("sh") # 根据value属性实现
select.select_by_visible_text("A北京") # 根据文本内容实现

十二、弹出框处理

网页中常用的弹出框有三种
1. alert 警告框
2. confirm 确认框
3. prompt 提示框

案例

需求:打开注册A.html页面,完成以下操作:
1).点击 alert 按钮
2).关闭警告框
3).输入用户名:admin

问题

1. 按钮被点击后弹出警告框,而接下来输入用户名的语句没有生效
2. 什么问题导致的?
3. 如何处理警告框?

弹出框处理方法

说明:Selenium中对处理弹出框的操作,有专用的处理方法;并且处理的方法都一样
1. 获取弹出框对象
alert = driver.switch_to.alert
2. 调用
alert.text --> 返回alert/confirm/prompt中的文字信息
alert.accept() --> 接受对话框选项
alert.dismiss() --> 取消对话框选项

示例代码

# 定位alerta按钮
driver.find_element_by_id("alerta").click()
# 获取警告框
alert = driver.switch_to.alert
# 打印警告框文本
print(alert.text)
# 接受警告框
alert.accept()
# 取消警告框
# alert.dismiss()

十三、滚动条操作

滚动条:一种可控制页面显示范围的组件


为什么要学习滚动条操作?

1. 在HTML页面中,由于前端技术框架的原因,页面元素为动态显示,元素根据滚动条的下拉而被加载
2. 页面注册同意条款,需要滚动条到最底层,才能点击同意

实现方式

说明:selenium中并没有直接提供操作滚动条的方法,但是它提供了可执行JavaScript脚本
的方法,所以我们可以通过JavaScript脚本来达到操作滚动条的目的.

1. 设置JavaScript脚本控制滚动条
js = "window.scrollTo(0,1000)"
(0:左边距;1000:上边距;单位像素)
2. selenium调用执行JavaScript脚本的方法
driver.execute_script(js)

案例

需求:打开注册页面A,暂停2秒后,滚动条拉到最底层

示例代码

# 最底层
js1 = "window.scrollTo(0,10000)"
driver.execute_script(js1)
# 最顶层
js2 = "window.scrollTo(0,0)"
driver.execute_script(js2)

十三、frame切换

目标

1. 掌握切换frame的方法
2. 掌握多窗口切换的技巧

frame:HTML页面中的一种框架,主要作用是在当前页面中指定区域显示另一页面元素;
形式一:[了解]
<frameset cols="25%,75%">
<frame src="frame_a.htm">
<frame src="frame_b.htm">
</frameset>
形式二:
<iframe name="iframe_a" src="demo_iframe.htm" width="200" height="200"></iframe>

为什么要学习frame切换

案例:打开‘注册实例.html’页面,完成以下操作
1). 填写主页面的注册信息
2). 填写注册页面A中的注册信息
3). 填写注册页面B中的注册信息

问题

1. 当前页面内无法定位注册页面A和注册页面B

frame切换方法

说明:在Selenium中封装了如何切换frame框架的方法
方法:
1). driver.switch_to.frame(frame_reference) --> 切换到指定frame的方法
frame_reference:可以为frame框架的name、id或者定位到的frame元素
2). driver.switch_to.default_content() --> 恢复默认页面方法

在frame中操作其他页面,必须先回到默认页面,才能进一步操作。


案例解决方案

1. 完成主页面注册信息;
2. 调用frame切换方法(switch_to.frame("myframe1"))切换到注册用户A框架中
3. 调用恢复默认页面方法(switch_to.default_content())
4. 调用frame切换方法(switch_to.frame("myframe2"))切换到注册用户B框架中

十四、多窗口切换

说明:在HTML页面中,当点击超链接或者按钮时,有的会在新的窗口打开页面。


为什么要切换窗口?

案例

需求:打开‘注册实例.html’页面,完成以下操作
1). 点击‘注册A页面’链接
2). 在打开的页面中,填写注册信息

问题

1). 无法定位注册A页面中的元素


如何实现多窗口切换?

说明:在Selenium中封装了获取当前窗口句柄、获取所有窗口句柄和切换到指定句柄窗口的方法;
句柄:英文handle,窗口的唯一识别码
方法:
1). driver.current_window_handle --> 获取当前窗口句柄
2). driver.window_handles --> 获取所有窗口句柄
3). driver.switch_to.window(handle) --> 切换指定句柄窗口

案例解决方案分析

1. 获取‘注册实例.html’当前窗口句柄
2. 点击‘注册实例.html’页面中注册A页面
3. 获取所有窗口句柄
4. 获取注册A页面对应的窗口句柄,并切换
5. 操作注册A页面元素

十六、窗口截图

目标

1. 掌握窗口截图方法
2. 熟悉验证码处理的方式

思考:如果自动化测试脚本运行时出现了异常,该如何定位问题?


窗口截图

说明:把当前操作的页面,截图保存到指定位置


为什么要窗口截图?

自动化脚本是由程序去执行的,因此有时候打印的错误信息并不是十分明确。如果在执行出错的时候
对当前窗口截图保存,那么通过图片就可以非常直观地看到出错的原因。

窗口截图的方法

说明:在Selenium中,提供了截图方法,我们只需要调用即可
方法:
driver.get_screenshot_as_file(imgpath)
imgpath:图片保存路径

案例

需求:打开‘注册A.html’页面,完成以下操作
1). 填写注册信息
2). 截图保存

示例代码

driver.find_element_by_id("userA").send_keys("admin")
driver.get_screenshot_as_file("./img/img01.jpg")

十七、 验证码

说明:一种随机生成的信息(数字、字母、汉字、图片、算术题)等为了防止恶意的请求行为,增加应用的安全性。


为什么要学习验证码?

在Web应用中,大部分系统在用户登录注册的时候都要求输入验证码,而我们在设计自动化测试脚本的时候,
就需要面临处理验证码的问题。

验证码的处理方式

说明:Selenium中并没有对验证码处理的方法,在这里我们介绍一下针对验证码的几种常用处理方式
方式:
1). 去掉验证码
(测试环境下-采用)
2). 设置万能验证码
(生产环境和测试环境下-采用)
3). 验证码识别技术
(通过Python-tesseract来识别图片类型验证码;识别率很难达到100%)
4). 记录cookie
(通过记录cookie进行跳过登录)

提示

1. 去掉验证码、设置万能验证码:都是开发来完成,我们在这里不做讲解
2. 验证码识别技术:成功率不高,验证码种类繁多,不太适合
3. 记录cookie:比较实用,我们对它进行下讲解

UnitTest框架

273afdc9f2e143deb7cfb897adca981d.png


目标:

1. 掌握UnitTest框架的基本使用方法
2. 掌握断言的使用方法
3. 掌握如何实现参数化
4. 掌握测试报告的生成

什么是框架?

说明:
1. 框架英文单词framework
2. 为解决一类事情的功能集合

一、UnitTest基本使用


什么是UnitTest框架?

概念:UnitTest是Python自带的一个单元测试框架,用它来做单元测试。

为什么使用UnitTest框架?

1. 能够组织多个用例去执行
2. 提供丰富的断言方法
3. 能够生成测试报告

UnitTest核心要素

1. TestCase
2. TestSuite
3. TestRunner
4. TestLoader
5. Fixture

TestCase

说明:TestCase就是测试用例

案例

定义一个实现加法操作的函数,并对该函数进行测试

定义测试用例

1. 导包:import unittest
2. 定义测试类:新建测试类必须继承unittest.TestCase
3. 定义测试方法:测试方法名称命名必须以test开头

执行测试用例

方式一:
使用pycharm在代码上点击鼠标右键,选择使用UnitTest运行
方式二:
调用 unittest.main() 来运行

思考:如何同时运行多个测试用例?


TestSuite

说明:(翻译:测试套件)多条测试用例集合在一起,就是一个TestSuite
使用:
1. 实例化: suite = unittest.TestSuite()
    (suite:为TestSuite实例化的名称)
2. 添加用例:suite.addTest(ClassName("MethodName"))
    (ClassName:为类名;MethodName:为方法名)
3. 添加扩展:suite.addTest(unittest.makeSuite(ClassName))
    (搜索指定ClassName内test开头的方法并添加到测试套件中)

提示:TestSuite需要配合TestRunner才能被执行


TextTestRunner

说明:TextTestRunner是用来执行测试用例和测试套件的
使用:
    1. 实例化: runner = unittest.TextTestRunner()
    2. 执行: runner.run(suite) # suite:为测试套件名称

需求

将test01.py..test10.py共10条用例,将这10条用例批量执行;

问题

  • 使用suite.addtest(unittest.makeSuite(className))导入10条测试类

  • .addtest()需要添加10次


TestLoader

说明:
用来加载TestCase到TestSuite中,即加载满足条件的测试用例,
并把测试用例封装成测试套件。使用unittest.TestLoader,
通过该类下面的discover()方法自动搜索指定目录下指定开头的.py文件,
并将查找到的测试用例组装到测试套件;
用法:
    suite = unittest.TestLoader().discover(test_dir, pattern='test*.py')
自动搜索指定目录下指定开头的.py文件,并将查找到的测试用例组装到测试套件
    test_dir: 为指定的测试用例的目录
    pattern:为查找的.py文件的格式,默认为'test*.py'
也可以使用unittest.defaultTestLoader 代替 unittest.TestLoader()
运行:
    runner = unittest.TextTestRunner()
    runner.run(suite)

TestLoaderTestSuite区别

1. TestSuite需要手动添加测试用例(可以添加测试类,也可以添加测试类中某个测试方法)
2. TestLoader搜索指定目录下指定开头.py文件,并添加测试类中的所有的测试方法,不能指定添加测试方法;

二、Fixture

目标

1. 掌握方法级别和类级别的Fixture
2. 了解模块级别的Fixture

Fixture

小需求:在一个测试类中定义多个测试方法,查看每个测试方法执行完所花费的时长。

说明:Fixture是一个概述,对一个测试用例环境的初始化和销毁就是一个Fixture
Fixture控制级别:
    1. 方法级别
    2. 类级别
    3. 模块级别

方法级别

使用
1. 初始化(前置处理):
    def setUp(self) --> 首先自动执行
2. 销毁(后置处理):
    def tearDown(self) --> 最后自动执行
3. 运行于测试方法的始末,即:运行一次测试方法就会运行一次setUp和tearDown

类级别

使用:
1. 初始化(前置处理):
@classmethod
    def setUpClass(cls): --> 首先自动执行
2. 销毁(后置处理):
@classmethod
    def tearDownClass(cls): --> 最后自动执行
3. 运行于测试类的始末,即:每个测试类只会运行一次setUpClass和tearDownClass

模块级别 [了解]

使用:
1. 初始化(前置处理):
    def setUpModule() --> 首先自动执行
2. 销毁(后置处理):
    def tearDownModule() --> 最后自动执行
3. 运行于整个模块的始末,即:整个模块只会运行一次setUpModule和tearDownModule

案例

需求:使用UnitTest框架对tpshop项目测试
1). 点击登录,进入登录页面
2). 输入用户名和密码,不输入验证码,直接点击登录按钮
3). 获取错误提示信息

示例代码

import time
import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://localhost")
self.driver.implicitly_wait(10)
self.driver.maximize_window()
def test_login(self):
# 点击登录按钮
self.driver.find_element_by_link_text("登录").click()
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
self.driver.find_element_by_id("password").send_keys("123456")
# 点击登录按钮
self.driver.find_element_by_css_selector("[name='sbtbutton']").click()
# 获取错误提示信息
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
print("msg=", msg)
​
def tearDown(self):
time.sleep(3)
self.driver.quit()

三、断言

目标

1. 理解什么是断言
2. 掌握断言assertEqual、assertIn方法
3. 了解UnitTest其他断言方法

什么是断言?

概念:让程序代替人为判断测试程序执行结果是否符合预期结果的过程


为什么要学习断言?

自动化脚本在执行的时候一般都是无人值守状态,我们不知道执行结果是否符合预期结果,所以我们需要让程序代替人为检测程序执行的结果是否符合预期结果,这就需要使用断言。

UnitTest断言方法

说明:
1. UnitTest中提供了非常丰富的断言方法,请参考附件资料
2. 复杂的断言方法在自动化测试中几乎使用不到,所以我们只需要掌握几个常用的即可

常用的UnitTest断言方法

0605771a33e54ef98a1d0fa25dc5561e.png

a08fbfb9426f4ecda3127097850a1e7f.png


使用方式

断言方法已经在unittest.TestCase类中定义好了,而且我们自定义的测试类已经继承了

TestCase,所以在测试方法中直接调用即可。

import unittest
def add(x, y):
return x + y
class TestAssert(unittest.TestCase):
def test01(self):
num = add(1, 2)
self.assertEqual(3, num)
def test02(self):
num = add(1, 2)
is_ok = num == 3
self.assertTrue(is_ok)

案例

需求:使用UnitTest框架对tpshop项目测试
1). 点击登录,进入登录页面
2). 输入用户名和密码,不输入验证码,直接点击登录按钮
3). 获取错误提示信息
4). 断言错误提示信息是否为“验证码不能为空!”,如果断言失败则保存截图
扩展:
1. 图片名称为动态-时间

断言主要代码

# 获取错误提示信息
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
print("msg=", msg)
try:
# 断言
self.assertIn("验证码不能为空", msg)
except AssertionError as e:
# 保存截图
img_path = "./imgs/img{}.png".format(time.strftime("%Y%m%d-%H%M%S"))
self.driver.get_screenshot_as_file(img_path)
raise e

案例代码

import time
import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://localhost")
self.driver.implicitly_wait(10)
self.driver.maximize_window()
def test_login(self):
# 点击登录按钮
  self.driver.find_element_by_link_text("登录").click()
# 输入用户名
  self.driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
  self.driver.find_element_by_id("password").send_keys("123456")
# 点击登录按钮
  self.driver.find_element_by_css_selector("[name='sbtbutton']").click()
# 获取错误提示信息
  msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
    print("msg=", msg)
  try:
# 断言
  self.assertIn("验证码不能为空", msg)
  except AssertionError as e:
# 保存截图
  img_path = "./imgs/img{}.png".format(time.strftime("%Y%m%d-%H%M%S"))
  self.driver.get_screenshot_as_file(img_path)
  raise e
def tearDown(self):
  self.driver.quit()
if __name__ == '__main__':
  unittest.main()

四、参数化

目标

掌握如何实现参数化


小需求

需求:定义一个实现加法操作的函数,并对该函数进行测试

示例代码

import unittest
# 求和
def add(x, y):
    return x + y
class TestAdd(unittest.TestCase):
  def test_add_01(self):
    result = add(1, 1)
    self.assertEqual(result, 2)
  def test_add_02(self):
    result = add(1, 0)
    self.assertEqual(result, 1)
  def test_add_03(self):
    result = add(0, 0)
    self.assertEqual(result, 0)
  def test_add(self):
    test_data = [(1, 1, 2), (1, 0, 1), (0, 0, 0)]
    for x, y, expect in test_data:
        print("x={} y={} expect={}".format(x, y, expect))
        result = add(x, y)
        self.assertEqual(result, expect)

发现问题

1. 一条测试数据定义一个测试函数,代码冗余度太高
2. 一个测试函数中测试多条数据,最终只会有一个测试结果

参数化

通过参数的方式来传递数据,从而实现数据和脚本分离。并且可以实现用例的重复执行。

unittest测试框架,本身不支持参数化,但是可以通过安装unittest扩展插件parameterized来实现。


安装

pip install parameterized

使用方式

  • 导包:from parameterized import parameterized

  • 使用@parameterized.expand装饰器可以为测试函数的参数进行参数化

@parameterized.expand([(1, 1, 2), (1, 0, 1), (0, 0, 0)])
def test_add(self, x, y, expect):
  pass
# 方式二
data = [(1, 1, 2), (1, 0, 1), (0, 0, 0)]
@parameterized.expand(data)
def test_add(self, x, y, expect):
  pass
# 方式三
def build_data():
  return [(1, 1, 2), (1, 0, 1), (0, 0, 0)]
​
@parameterized.expand(build_data)
def test_add(self, x, y, expect):
  pass

示例代码

import unittest
from parameterized import parameterized
# 求和
def add(x, y):
return x + y
# 构建测试数据
def build_data():
  return [(1, 1, 2), (1, 0, 1), (0, 0, 0)]
  
class TestAdd(unittest.TestCase):
@parameterized.expand([(1, 1, 2), (1, 0, 1), (0, 0, 0)])
  def test_add_1(self, x, y, expect):
    print("x={} y={} expect={}".format(x, y, expect))
    result = add(x, y)
    self.assertEqual(result, expect)
    
    data = [(1, 1, 2), (1, 0, 1), (0, 0, 0)]
    
@parameterized.expand(data)
def test_add_2(self, x, y, expect):
    print("x={} y={} expect={}".format(x, y, expect))
    result = add(x, y)
    self.assertEqual(result, expect)
​
@parameterized.expand(build_data)
def test_add_3(self, x, y, expect):
    print("x={} y={} expect={}".format(x, y, expect))
    result = add(x, y)
    self.assertEqual(result, expect)

五、跳过

目标

1. 掌握如何把测试函数标记成跳过

跳过

对于一些未完成的或者不满足测试条件的测试函数和测试类,可以跳过执行。

使用方式

# 直接将测试函数标记成跳过
@unittest.skip('代码未完成')
# 根据条件判断测试函数是否跳过
@unittest.skipIf(condition, reason)

示例代码

import unittest
version = 35
class TestSkip(unittest.TestCase):
@unittest.skip("代码未完成")
  def test_01(self):
    print("test_01")
@unittest.skipIf(version <= 30, "版本大于30才会执行")
  def test_02(self):
    print("test_02")
@unittest.skip("代码未完成")
class TestSkip2(unittest.TestCase):
  def test_a(self):
    print("test_a")
  def test_b(self):
    print("test_b")

六、生成HTML测试报告

目标

1. 掌握如何生成HTML测试报告方法

什么是HTML测试报告

说明:HTML测试报告就是执行完测试用例后,以HTML(网页)方式将执行结果生成报告

为什么要生成测试报告

1. 测试报告是本次测试结果的体现形态
2. 测试报告内包含了有关本次测试用例的详情

HTML生成报告方式

1. Export Test Results (UnitTest 自带)
2. HTMLTestRunner(第三方模板)【重点】

Export Test Results (自带)

测试报告截图

f8e0703ab0634eb5859057baa6fefe81.png


自带报告生成操作图

86275bf91d5447c9898cd82e45240a47.png


HTMLTestRunner【重点】

测试报告截图

c7fa3affb7b24e28846b16612b2b3062.png


测试报告 生成步骤分析

1. 复制HTMLTestRunner.py文件到项目文件夹
2. 导入HTMLTestRunner、unittest包
3. 生成测试套件
  suite = unittest.TestSuite()
  suite.addTest(TestLogin("test_login"))
​
  suite = unittest.defaultTestLoader.discover(test_dir,pattern="test*.py")
4. 设置报告生成路径和文件名
  file_name = "./report/report.html"
5. 打开报告 with open(file_name,'wb') as f:
6. 实例化HTMLTestRunner对象:
  runner = HTMLTestRunner(stream=f,[title],[description])
参数说明:
  stream:文件流,打开写入报告的名称及写入编码格式)
  title:[可选参数],为报告标题,如XXX自动化测试报告
  description:[可选参数],为报告描述信息;比如操作系统、浏览器等版本
7. 执行:runner.run(suite)

实现代码

import time
import unittest
from day05.tools.HTMLTestRunner import HTMLTestRunner
# 加载指定目录下的测试用例文件
suite = unittest.defaultTestLoader.discover("./case/", "test*.py")
# 报告文件存放路径
report_path = "./report/report{}.html".format(time.strftime("%Y%m%d%H%M%S"))
with open(report_path, "wb") as f:
  # 实例化HTMLTestRunner对象,传入报告文件流f
  runner = HTMLTestRunner(stream=f, title="自动化测试报告", description="FireFox浏览器")
  runner.run(suite)

PO模式

b683472925f6436ba5ba5e03938b789e.png


目录:

1. 深入理解方法封装的思想
2. 能够使用方法封装的思想对代码进行优化
3. 深入理解PO模式的思想
4. 熟练掌握PO模式的分层思想

PO模式学习思路

采用版本迭代的方式来学习,便于对不同版本的优缺点进行对比和理解。

  • V1:不使用任何设计模式和单元测试框架

  • V2:使用UnitTest管理用例

  • V3:使用方法封装的思想,对代码进行优化

  • V4:采用PO模式的分层思想对代码进行拆分

  • V5:对PO分层之后的代码继续优化

  • V6:PO模式深入封装,把共同操作提取封装到父类中,子类直接调用父类的方法


一、无模式

目标

1. 熟悉web自动化测试代码编写的基本流程
2. 掌握如何使用UnitTest管理测试脚本

案例说明

对TPshop项目的登录模块进行自动化测试。

提示:登录模块包含了很多测试用例,比如:
    账号不存在、密码错误、验证码错误、登录成功等等。
为了节省时间我们只选取几个有代表性的用例来演示。

选择的测试用例

账号不存在
1. 点击首页的‘登录’链接,进入登录页面
2. 输入一个不存在的用户名
3. 输入密码
4. 输入验证码
5. 点击登录按钮
6. 获取错误提示信息
密码错误
1. 点击首页的‘登录’链接,进入登录页面
2. 输入用户名
3. 输入一个错误的密码
4. 输入验证码
5. 点击登录按钮
6. 获取错误提示信息

V1版本

不使用任何设计模式和单元测试框架。

每个文件里编写一个用例,完全的面向过程的编程方式。


存在的问题

  • 一条测试用例对应一个文件,用例较多时不方便管理维护

  • 代码高度冗余


示例代码

登录功能-账号不存在

from selenium import webdriver
# 创建浏览器驱动对象,并完成初始化操作
driver = webdriver.Firefox()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost")
"""
登录功能-账号不存在
"""
# 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_link_text("登录").click()
# 输入用户名
driver.find_element_by_id("username").send_keys("13099999999")
# 输入密码
driver.find_element_by_id("password").send_keys("123456")
# 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’按钮
driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
msg = driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
# 关闭驱动对象
driver.quit()

登录功能-密码错误

from selenium import webdriver
# 创建浏览器驱动对象,并完成初始化操作
driver = webdriver.Firefox()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost")
"""
登录功能-密码错误
"""
# 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_link_text("登录").click()
# 输入用户名
driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
driver.find_element_by_id("password").send_keys("error")
# 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’按钮
driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
msg = driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
# 关闭驱动对象
driver.quit()

V2版本

使用UnitTest管理用例,并断言用例的执行结果


引入UnitTest的好处

  • 方便组织、管理多个测试用例

  • 提供了丰富的断言方法

  • 方便生成测试报告

  • 减少了代码冗余


存在的问题

代码冗余


示例代码

import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
"""
对登录模块的功能进行测试
"""
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Firefox()
cls.driver.maximize_window()
cls.driver.implicitly_wait(10)
cls.driver.get("http://localhost")
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def setUp(self):
# 打开首页
self.driver.get("http://localhost")
# 点击首页的‘登录’链接,进入登录页面
self.driver.find_element_by_link_text("登录").click()
# 账号不存在
def test_login_username_is_error(self):
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13099999999")
# 输入密码
self.driver.find_element_by_id("password").send_keys("123456")
# 输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’
self.driver.find_element_by_name("sbtbutton").click()
# 断言提示信息
msg = self.driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
self.assertIn("账号不存在", msg)
# 密码错误
def test_login_password_is_error(self):
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
self.driver.find_element_by_id("password").send_keys("error")
# 输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’
self.driver.find_element_by_name("sbtbutton").click()
# 断言提示信息
msg = self.driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
self.assertIn("密码错误", msg)

二、方法封装

目标

1. 深入理解方法封装的思想
2. 能够使用方法封装的思想对代码进行优化

方法封装

方法封装:是将一些有共性的或多次被使用的代码提取到一个方法中,供其他地方调用。


封装的好处:

  • 避免代码冗余

  • 容易维护

  • 隐藏代码实现的细节

目的:用最少的代码实现最多的功能


V3版本

使用方法封装的思想,对代码进行优化。

  • 定义获取驱动对象的工具类

  • 封装“获取弹出框的提示消息”


定义获取驱动对象的工具类

对登录流程的代码进行优化,定义获取驱动对象的工具类

# utils.py
class DriverUtil:
"""
浏览器驱动工具类
"""
_driver = None
@classmethod
​
def get_driver(cls):
"""
获取浏览器驱动对象,并完成初始化设置
:return: 浏览器驱动对象
"""
if cls._driver is None:
    cls._driver = webdriver.Firefox()
    cls._driver.maximize_window()
    cls._driver.implicitly_wait(10)
    cls._driver.get("http://localhost")
  return cls._driver
  
@classmethod
def quit_driver(cls):
"""
关闭浏览器驱动
"""
if cls._driver:
  cls._driver.quit()
  cls._driver = None
  
# utils.py
def get_tips_msg():
  """
  获取弹出框的提示消息
  :return: 消息文本内容
  """
  msg = DriverUtil.get_driver().find_element_by_class_name("layui-layer-content").text
  return msg

三、PO模式介绍

目标

1. 深入理解PO模式的思想
2. 熟练掌握PO模式的分层思想

存在的问题

在做UI自动化时定位元素特别依赖页面,一旦页面发生变更就不得不跟着去修改定位元素的代码。

举例:假设要对一个元素进行点击操作,而且会经常对该元素进行操作,那么你就可能会编写多处如下代码

driver.find_element_by_id("login-btn").click()

存在的问题:

  • 如果开发人员修改了这个元素的id,这时候你就不得不修改所有对应的代码

  • 存在大量冗余代码

思考:如何来解决这个问题呢?


PO模式

PO是Page Object的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一。

核心思想是通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化, 只需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。

PO模式可以把一个页面分为三层,对象库层、操作层、业务层。

  • 对象库层:封装定位元素的方法。

  • 操作层:封装对元素的操作。

  • 业务层:将一个或多个操作组合起来完成一个业务功能。比如登录:需要输入帐号、密码、点击登录三个操作。


引入PO模式的好处

引入PO模式前

  • 存在大量冗余代码

  • 业务流程不清晰

  • 后期维护成本大


引入PO模式后

  • 减少冗余代码

  • 业务代码和测试代码被分开,降低耦合性

  • 维护成本低


四、PO模式实践

目标

1. 能够采用PO模式的分层思想对页面进行封装

V4版本

采用PO模式的分层思想对代码进行拆分


PO分层封装

对登录页面进行分层封装:

  • 对象库层:LoginPage

  • 操作层:LoginHandle

  • 业务层:LoginProxy

调用业务层的方法,编写测试用例:

  • 测试用例:TestLogin


示例代码

from po.utils import DriverUtil
class LoginPage:
"""
对象库层
"""
def __init__(self):
  self.driver = DriverUtil.get_driver()
  # 用户名输入框
  self.username = None
  # 密码
  self.password = None
  # 验证码输入框
  self.verify_code = None
  
  # 登录按钮
self.login_btn = None
# 忘记密码
self.forget_pwd = None
  def find_username(self):
    return self.driver.find_element_by_id("username")
  def find_password(self):
    return self.driver.find_element_by_id("password")
  def find_verify_code(self):
    return self.driver.find_element_by_id("verify_code")
  def find_login_btn(self):
    return self.driver.find_element_by_name("sbtbutton")
  def find_forget_pwd(self):
    return self.driver.find_element_by_partial_link_text("忘记密码")
class LoginHandle:
"""
操作层
"""
def __init__(self):
  self.login_page = LoginPage()
def input_username(self, username):
  self.login_page.find_username().send_keys(username)
def input_password(self, pwd):
  self.login_page.find_password().send_keys(pwd)
def input_verify_code(self, code):
  self.login_page.find_verify_code().send_keys(code)
def click_login_btn(self):
  self.login_page.find_login_btn().click()
def click_forget_pwd(self):
  self.login_page.find_forget_pwd().click()
class LoginProxy:
"""
业务层
"""
def __init__(self):
  self.login_handle = LoginHandle()
# 登录
def login(self, username, password, verify_code):
# 输入用户名
  self.login_handle.input_username(username)
# 输入密码
  self.login_handle.input_password(password)
# 输入验证码
  self.login_handle.input_verify_code(verify_code)
# 点击登录按钮
  self.login_handle.click_login_btn()
# 跳转到忘记密码页面
def to_forget_pwd_page(self):
# 点击忘记密码
  self.login_handle.click_forget_pwd()

import unittest
from po import utils
from po.utils import DriverUtil
from po.v4.page.login_page import LoginProxy
class TestLogin(unittest.TestCase):
"""
对登录模块的功能进行测试
"""
@classmethod
  def setUpClass(cls):
    cls.driver = DriverUtil.get_driver()
    cls.login_proxy = LoginProxy()
@classmethod
def tearDownClass(cls):
  DriverUtil.quit_driver()
def setUp(self):
# 打开首页
  self.driver.get("http://localhost")
# 点击首页的‘登录’链接,进入登录页面
  self.driver.find_element_by_link_text("登录").click()
  # 账号不存在
def test_login_username_is_error(self):
  self.login_proxy.login("13099999999", "123456", "8888")
# 断言提示信息
  msg = utils.get_tips_msg()
  print("msg=", msg)
  self.assertIn("账号不存在", msg)
# 密码错误
def test_login_password_is_error(self):
  self.login_proxy.login("13012345678", "123456", "8888")
# 断言提示信息
  msg = utils.get_tips_msg()
  print("msg=", msg)
  self.assertIn("密码错误", msg)

V5版本

对PO分层之后的代码继续优化

  • 优化对象库层的代码,抽取元素的定位方式,把定位信息定义在对象的属性中,便于集中管理

  • 优化操作层的代码,针对输入操作应该先清空输入框中的内容再输入新的内容
from selenium.webdriver.common.by import By
from po.utils import DriverUtil
class LoginPage:
"""
对象库层
"""
def __init__(self):
  self.driver = DriverUtil.get_driver()
# 用户名
  self.username = (By.ID, "username")
# 密码
  self.password = (By.ID, "password")
# 验证码输入框
  self.verify_code = (By.ID, "verify_code")
# 登录按钮
  self.login_btn = (By.NAME, "sbtbutton")
​
# 忘记密码
  self.forget_pwd = (By.PARTIAL_LINK_TEXT, "忘记密码")
def find_username(self):
  return self.driver.find_element(self.username[0],     self.username[1])
def find_password(self):
  return self.driver.find_element(self.password[0],   self.password[1])
def find_verify_code(self):
  return self.driver.find_element(self.verify_code[0],   self.verify_code[1])
def find_login_btn(self):
  return self.driver.find_element(self.login_btn[0],   self.login_btn[1])
def find_forget_pwd(self):
  return self.driver.find_element(self.forget_pwd[0],   self.forget_pwd[1])
class LoginHandle:
"""
操作层
"""
def __init__(self):
  self.login_page = LoginPage()
def input_username(self, username):
  self.login_page.find_username().clear()
  self.login_page.find_username().send_keys(username)
def input_password(self, pwd):
  self.login_page.find_password().clear()
  self.login_page.find_password().send_keys(pwd)
def input_verify_code(self, code):
  self.login_page.find_verify_code().clear()
  self.login_page.find_verify_code().send_keys(code)
def click_login_btn(self):
  self.login_page.find_login_btn().click()
def click_forget_pwd(self):
  self.login_page.find_forget_pwd().click()
class LoginProxy:
"""
业务层
"""
def __init__(self):
  self.login_handle = LoginHandle()
# 登录
def login(self, username, password, verify_code):
# 输入用户名
  self.login_handle.input_username(username)
# 输入密码
  self.login_handle.input_password(password)
# 输入验证码
  self.login_handle.input_verify_code(verify_code)
# 点击登录按钮
  self.login_handle.click_login_btn()
# 跳转到忘记密码页面
def to_forget_pwd_page(self):
# 点击忘记密码
  self.login_handle.click_forget_pwd()

五、PO模式深入封装

目标

1. 能够采用继承的思想对PO模式进行深入的封装

V6版本

把共同操作提取封装到父类中,子类直接调用父类的方法,避免代码冗余

  • 对象库层-基类,把定位元素的方法定义在基类中

  • 操作层-基类,把对元素执行输入操作的方法定义在基类中


示例代码

# base_page.py
from po.utils import DriverUtil
class BasePage:
"""
基类-对象库层
"""
def __init__(self):
  self.driver = DriverUtil.get_driver()
def find_element(self, location):
  return self.driver.find_element(location[0], location[1])
class BaseHandle:
"""
基类-操作层
"""
def input_text(self, element, text):
"""
在输入框里输入文本内容,先清空再输入
:param element: 要操作的元素
:param text: 要输入的文本内容
"""
element.clear()
element.send_keys(text)

from selenium.webdriver.common.by import By
from po.v6.common.base_page import BasePage, BaseHandle
class LoginPage(BasePage):
"""
对象库层
"""
def __init__(self):
super().__init__()
# 用户名输入框
self.username = (By.ID, "username")
# 密码
self.password = (By.ID, "password")
# 验证码
self.verify_code = (By.ID, "verify_code")
# 登录按钮
self.login_btn = (By.NAME, "sbtbutton")
# 忘记密码
  self.forget_pwd = (By.PARTIAL_LINK_TEXT, "忘记密码")
def find_username(self):
  return self.find_element(self.username)
def find_password(self):
  return self.find_element(self.password)
def find_verify_code(self):
  return self.find_element(self.verify_code)
def find_login_btn(self):
  return self.find_element(self.login_btn)
def find_forget_pwd(self):
  return self.find_element(self.forget_pwd)
class LoginHandle(BaseHandle):
"""
操作层
"""
def __init__(self):
  self.login_page = LoginPage()
def input_username(self, username):
  self.input_text(self.login_page.find_username(), username)
def input_password(self, pwd):
  self.input_text(self.login_page.find_password(), pwd)
def input_verify_code(self, code):
  self.input_text(self.login_page.find_verify_code(), code)
def click_login_btn(self):
  self.login_page.find_login_btn().click()
def click_forget_pwd(self):
  self.login_page.find_forget_pwd().click()
class LoginProxy:
"""
业务层
"""
def __init__(self):
  self.login_handle = LoginHandle()
# 登录
def login(self, username, password, verify_code):
# 输入用户名
  self.login_handle.input_username(username)
# 输入密码
  self.login_handle.input_password(password)
# 输入验证码
  self.login_handle.input_verify_code(verify_code)
# 点击登录按钮
  self.login_handle.click_login_btn()
# 跳转到忘记密码页面
def to_forget_pwd_page(self):
# 点击忘记密码
  self.login_handle.click_forget_pwd()

数据驱动

目标

1. 理解什么是数据驱动
2. 熟练掌握对JSON数据的操作
3. 能够使用数据驱动对代码进行优化

一、数据驱动介绍

目标

理解数据驱动的概念


什么是数据驱动?

数据驱动:是以数据来驱动整个测试用例的执行,也就是测试数据决定测试结果。

比如我们要测试加法,我们的测试数据是1和1,测试结果就是2,如果测试数据是1和2,测试结果就是3。


数据驱动的特点

  • 数据驱动本身不是一个工业级标准的概念,因此在不同的公司都会有不同的解释。

  • 可以把数据驱动理解为一种模式或者一种思想。

  • 数据驱动技术可以将用户把关注点放在对测试数据的构建和维护上,而不是直接维护脚本,可以利用同样的过程对不同的数据输入进行测试。

  • 数据驱动的实现要依赖参数化的技术。


传入数据的方式(测试数据的来源)

  • 直接定义在测试脚本中(简单直观,但代码和数据未实现真正的分离,不方便后期维护)

  • 从文件读取数据,如JSON、excel、xml、txt等格式文件

  • 从数据库中读取数据

  •  

  • 直接调用接口获取数据源本地封装一些生成数据的方法


二、JSON操作

目标

掌握JSON的语法格式熟练
掌握对JSON数据的操作

JSON介绍

JSON的全称是”JavaScript Object Notation”,是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式。


JSON特点

- JSON是纯文本
- JSON具有良好的自我描述性,便于阅读和编写
- JSON具有清晰的层级结构
- 有效地提升网络传输效率

2bf17a76e25c48f888d68cf9e51429fe.png


JSON语法规则

- 大括号保存对象
- 中括号保存数组
- 对象数组可以相互嵌套
- 数据采用键值对表示
- 多个数据由逗号分隔

JSON

JSON 值可以是:

  • 数字(整数或浮点数)

  • 字符串(在双引号中)

  • 逻辑值(true 或 false)

  • 数组(在中括号中)

  • 对象(在大括号中)

  • null


示例:

  {
    "name": "tom",
    "age": 18,
    "isMan": true,
    "school": null,
    "address": {
    "country": "中国",
    "city": "江苏苏州",
    "street": "科技园路"
  },
  "numbers": [2, 6, 8, 9],
  "links": [
  {
    "name": "Baidu",
    "url": "http://www.baidu.com"
  },
{
  "name": "TaoBao",
  "url": "http://www.taobao.com"
}]}

数据操作

  • python字典与JSON之间的转换

  • JSON文件读写


导入依赖包

import json

python字典与JSON之间的转换

python字典类型转换为 JSON字符串

data = {
  'id': 1,
  'name': 'Tom',
  'address': '北京市海淀区',
  'school': None
}
  json_str = json.dumps(data)

JSON字符串转换为python字典

json_str = '{"id": 1, "name": "Tom", "address": "北京市海淀区", "school": null}'
dict_data = json.loads(json_str)

JSON文件读写

读取JSON文件

with open('data.json', encoding='UTF-8') as f:
  data = json.load(f) # 返回的数据类型为字典或列表 

写入JSON文件

param = {'name': 'tom', 'age': 20}
with open('data2.json', 'w', encoding='UTF-8') as f:
  json.dump(param, f)

三、数据驱动实战一

目标

1. 掌握数据驱动的开发流程
2. 掌握如何读取JSON数据文件
3. 巩固PO模式

案例

对网页计算器,进行加法的测试操作。通过读取数据文件中的数据来执行用例。
网址:http://cal.apple886.com/

c45b983f40db4f9ca89e86a7d99371e6.png


实现步骤

  • 采用PO模式的分层思想对页面进行封装

  • 编写测试脚本

  • 使用参数化传入测试数据

  • 把测试数据定义到JSON数据文件中


数据文件

第一个数字加第二个数字等于第三个数字,每一行数据代表一个用例

0c5fcf8f7c274ec7821fe134998dc2ea.png


示例代码

from selenium import webdriver
class DriverUtil:
    """
    浏览器驱动工具类
    """
    _driver = None
​
    @classmethod
    def get_driver(cls):
      """
      获取浏览器驱动对象,并完成初始化设置
      :return: 浏览器驱动对象
      """
    if cls._driver is None:
      cls._driver = webdriver.Chrome()
      cls._driver.maximize_window()
      cls._driver.implicitly_wait(10)
      cls._driver.get("http://cal.apple886.com/")
    return cls._driver
    @classmethod
    def quit_driver(cls):
      """
      关闭浏览器驱动
      """
      if cls._driver:
        cls._driver.quit()
        cls._driver = None

from selenium.webdriver.common.by import By
from ddt.calculator.utils import DriverUtil
class CalculatorPage:
"""
计算器页面-对象库层
"""
def __init__(self):
  self.driver = DriverUtil.get_driver()
# 数字按钮
  self.digit_btn = (By.ID, "simple{}")
# 加法按钮
  self.add_btn = (By.ID, "simpleAdd")
# 等号按钮
  self.eq_btn = (By.ID, "simpleEqual")
# 计算结果
  self.result = (By.ID, "resultIpt")
def find_digit_btn(self, digit):
  location = (self.digit_btn[0],   self.digit_btn[1].format(digit))
    return self.driver.find_element(*location)
def find_add_btn(self):
  return self.driver.find_element(*self.add_btn)
def find_eq_btn(self):
  return self.driver.find_element(*self.eq_btn)
def find_result_btn(self):
  return self.driver.find_element(*self.result)
class CalculatorHandle:
"""
计算器页面-操作层
"""
def __init__(self):
  self.calculator_page = CalculatorPage()
def click_digit_btn(self, digit):
​
  self.calculator_page.find_digit_btn(digit).click()
def click_add_btn(self):
  self.calculator_page.find_add_btn().click()
def click_eq_btn(self):
  self.calculator_page.find_eq_btn().click()
def get_result(self):
return      self.calculator_page.find_result_btn().get_attribute("value")
def input_numbers(self, numbers):
for num in numbers:
  self.click_digit_btn(num)
class CalculatorProxy:
"""
计算器页面-业务层
"""
def __init__(self):
  self.calculator_handle = CalculatorHandle()
def add(self, num1, num2):
  self.calculator_handle.input_numbers(str(num1))
  self.calculator_handle.click_add_btn()
  self.calculator_handle.input_numbers(str(num2))
  self.calculator_handle.click_eq_btn()
def get_result(self):
return self.calculator_handle.get_result()

import json
import time
import unittest
from parameterized import parameterized
from ddt.calculator.page.calculator_page import CalculatorProxy
from ddt.calculator.utils import DriverUtil
def build_data():
  test_data = []
  with open("../data/calculator.json", encoding='UTF-8') as f:
  test_data = json.load(f)
  
  print("test_data=", test_data)
  return test_data
class TestCalculator(unittest.TestCase):
@classmethod
def setUpClass(cls):
  cls.driver = DriverUtil.get_driver()
  cls.calculatorProxy = CalculatorProxy()
@classmethod
  def tearDownClass(cls):
  DriverUtil.quit_driver()
@parameterized.expand(build_data)
  def test_add(self, a, b, expect):
  print('a={} b={} expect={}'.format(a, b, expect))
  self.calculatorProxy.add(a, b)
# 获取计算结果
    result = self.calculatorProxy.get_result()
print("result=", result)
  self.assertEqual(result, str(expect))

日志收集

目标

1. 理解日志的相关概念
2. 掌握日志的基本用法
3. 掌握日志的高级用法

一、日志相关概念

目标

1. 了解日志的概念
2. 理解日志的作用
3. 掌握常见的日志级别

日志

概念:日志就是用于记录系统运行时的信息,对一个事件的记录;也称为Log。


日志的作用

- 调试程序
- 了解系统程序运行的情况,是否正常
- 系统程序运行故障分析与问题定位
- 用来做用户行为分析和数据统计

日志级别

思考:是否系统记录的所有日志信息的重要性都一样?

日志级别:是指日志信息的优先级、重要性或者严重程度

常见的日志级别

71a069454d8d4c9e9bfac3ace726eaa5.png


说明

  • 上面列表中的日志级别是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL;

  • 当为程序指定一个日志级别后,程序会记录所有日志级别大于或等于指定日志级别的日志信息,而不是仅仅记录指定级别的日志信息;

  • 一般建议只使用DEBUG、INFO、WARNING、ERROR这四个级别


二、日志的基本用法

目标

1. 掌握如何设置日志级别
2. 掌握如何设置日志格式
3. 掌握如何将日志信息输出到文件中

logging模块

Python中有一个标准库模块logging可以直接记录日志


基本用法

import logging
​
logging.debug("这是一条调试信息")
logging.info("这是一条普通信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")

设置日志级别

logging中默认的日志级别为WARNING,程序中大于等于该级别的日志才能输出,小于该级别的日志不会被打印出来。


设置日志级别

logging.basicConfig(level=logging.DEBUG)

如何选择日志级别

  • 在开发环境和测试环境中,为了尽可能详细的查看程序的运行状态来保证上线后的稳定性,可以使用DEBUG或INFO级别的日志获取详细的日志信息,这是非常耗费机器性能的.

  • 在生产环境中,通常只记录程序的异常信息、错误信息等(设置成WARNING或ERROR级别),这样既可以减小服务器的I/O压力,也可以提高获取错误日志信息的效率和方便问题的排查。


设置日志格式

默认的日志的格式为:
日志级别:Logger名称:日志内容
​
自定义日志格式:
logging.basicConfig(format="%(levelname)s:%(name)s:%(message)s")

format参数中可能用到的格式化信息:

7fbdfb65e1ac4380afc338691a3301eb.png


示例代码:

import logging
fmt = '%(asctime)s %(levelname)s [%(name)s] 
    [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
logging.basicConfig(level=logging.INFO, format=fmt)
​
logging.debug("调试")
logging.info("信息")
logging.warning("警告")
logging.error("错误")

将日志信息输出到文件中

默认情况下Python的logging模块将日志打印到了标准输出中(控制台)

将日志信息输出到文件中:

logging.basicConfig(filename="a.log")

示例代码:

import logging
​
fmt = '%(asctime)s %(levelname)s [%(name)s] 
  [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
logging.basicConfig(filename="a.log", level=logging.INFO, format=fmt)
logging.debug("调试")
logging.info("信息")
logging.warning("警告")
logging.error("错误")

三、日志的高级用法

目标

1. 了解logging日志模块四大组件
2. 掌握如何讲日志输出到多个Handler中

思考:

  • 如何将日志信息同时输出到控制台和日志文件中?

  • 如何将不同级别的日志输出到不同的日志文件中?

  • 如何解决日志文件过大的问题?


logging日志模块四大组件

4984811393904aa1902331a313288c8d.png


logging模块就是通过这些组件来完成日志处理的


组件之间的关系

  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;

  • 不同的处理器(handler)可以将日志输出到不同的位置;

  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;

  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;


简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。


Logger

Logger对象的任务:

  • 向程序暴露记录日志的方法

  • 基于日志级别或Filter对象来决定要对哪些日志进行后续处理

  • 将日志消息传送给所有感兴趣的日志handlers


如何创建Logger对象

logger = logging.getLogger()
logger = logging.getLogger("myLogger")

logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则返回root日志器对象。 若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。


Logger常用的方法

882b6a49860d4069801ed34658507cb7.png


Handler

Handler对象的作用是将消息分发到handler指定的位置,比如:控制台、文件、网络、邮件等。 Logger对象可以通过addHandler()方法为自己添加多个handler对象。


如何创建Handler对象

在程序中不应该直接实例化和使用Handler实例,因为Handler是一个基类,它只定义了Handler应该有的接口。 应该使用Handler实现类来创建对象,logging中内置的常用的Handler包括:

92e0d62a790c4cb9be9a7e3d7cac7521.png


Handler常用的方法

6c2c495c5f9b4452b43ebc420f6d1fdf.png

813a8bef20434fecab1729edb8bf2ade.png


Formatter

Formatter对象用于配置日志信息的格式。


如何创建Formatter对象

formatter = logging.Formatter(fmt=None, datefmt=None, style='%')
  fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
  datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
  style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'

将日志信息同时输出到控制台和文件中

实现步骤分析

1.创建日志器对象
2.创建控制台处理器对象
3.创建文件处理器对象
4.创建格式化器对象
5.把格式化器添加到处理器中
6.把处理器添加到日志器中

定义日志格式

fmt = '%(asctime)s %(levelname)s [%(name)s] 
    [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt)

把日志输出到控制台

logger = logging.getLogger()
sh = logging.StreamHandler()
sh.setFormatter(formatter)
logger.addHandler(sh)

把日志输出到文件中

fh = logging.FileHandler("./b.log")
fh.setFormatter(formatter)
logger.addHandler(fh)

每日生成一个日志文件

定义Handler对象

fh = logging.handlers.TimedRotatingFileHandler(filename,
     when='h', interval=1, backupCount=0)
  将日志信息记录到文件中,以特定的时间间隔切换日志文件。
  filename: 日志文件名
  when: 时间单位,可选参数
    S - Seconds
    M - Minutes
    H - Hours
    D - Days
    midnight - roll over at midnight
    W{0-6} - roll over on a certain day; 0 - Monday
interval: 时间间隔
backupCount: 日志文件备份数量。如果backupCount大于0,那么当生成新的日志文件时,
     将只保留backupCount个文件,删除最老的文件。

示例代码:

import logging.handlers
​
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
​
# 日志格式
fmt = "%(asctime)s %(levelname)s 
    [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
formatter = logging.Formatter(fmt)
​
# 输出到文件,每日一个文件
fh = logging.handlers.TimedRotatingFileHandler("./a.log",
     when='MIDNIGHT', interval=1, backupCount=3)
fh.setFormatter(formatter)
fh.setLevel(logging.INFO)
logger.addHandler(fh)

项目实战

目标

1. 熟悉自动化测试的流程
2. 能够对一个web项目实现自动化测试
3. 熟练使用selenium常用的API
4. 能够把UnitTest应用到项目中
5. 能够把PO模式应用到项目中
6. 能够把数据驱动应用到项目中
7. 能够把日志收集功能应用到项目中

一、自动化测试流程

目标

1. 熟悉自动化测试的流程

自动化测试的流程

1. 需求分析
2. 挑选适合做自动化测试的功能
3. 设计测试用例
4. 搭建自动化测试环境 [可选]
5. 设计自动化测试项目的架构 [可选]
6. 编写代码
7. 执行测试用例
8. 生成测试报告并分析结果

二、项目介绍

项目名称

TPshop开源商城系统


项目描述

TPshop是一个电子商务B2C电商平台系统,功能强大,安全便捷。适合企业及个人快速构建个性化网上商城。

包含PC+IOS客户端+Adroid客户端+微商城,系统PC+后台是基于ThinkPHP MVC构架开发的跨平台开源软件,设计得非常灵活,具有模块化架构体系和丰富的功能,易于与第三方应用系统无缝集成,在设计上,包含相当全面,以模块化架构体系,让应用组合变得相当灵活,功能也相当丰富。


项目架构

87f4e4ac72d54a77910e9366793fa007.png


三、用例设计

目标

1. 掌握如何编写自动化测试用例文档

编写自动化测试用例的原则

1. 自动化测试用例一般只实现核心业务流程或者重复执行率较高的功能。
2. 自动化测试用例的选择一般以“正向”逻辑的验证为主。
3. 不是所有手工用例都可以使用自动化测试来执行。
4. 尽量减少多个用例脚本之间的依赖。
5. 自动化测试用例执行完毕之后,一般需要回归原点。

编写测试用例

4aa3b9d5c10a41559db3cbb80a281dbd.png


四、项目搭建

目标

1. 掌握如何进行自动化测试框架的搭建

初始化项目

项目名称:webAutoTestTPshop


创建目录结构

c3fe3458be4c4499a04306f46f2c23e4.png


安装依赖包

  • 安装 selenium 包

  • 安装 parameterized 包

  • 添加 HTMLTestRunner


初始化代码

  • 封装驱动工具类

  • 封装PO基类,定义 BasePage 和 BaseHandle


五、编写代码

目标

1. 掌握如何采用PO模式的分层思想对页面进行封装
2. 掌握如何使用UnitTest管理项目中的测试用例

抽取PO

根据用例分析待测功能,提取页面对象


定义页面对象文件

登录页:login_page.py
首页:index_page.py
后台页面(个人中心页):home_page.py
商品搜索页:goods_search_page.py
商品详情页:goods_detail_page.py
购物车页:cart_page.py
下订单页:order_page.py
订单支付页:order_pay_page.py
我的订单页:my_order_page.py

分别编写对象库层、操作层、业务层的代码


编写测试脚本

定义测试脚本文件

登录模块:test_login.py
购物车模块:test_cart.py
订单模块:test_order.py

使用unittest管理测试脚本


执行测试脚本

  • 使用unittest执行测试脚本

  • 调试代码


六、完善代码

目标

1. 掌握如何把数据驱动应用到项目中
2. 能够把日志收集功能应用到项目中
3. 掌握如何使用UnitTest生成测试报告

数据驱动

定义数据文件

  • 定义存放测试数据的目录,目录名称:data

  • 分模块定义数据文件

登录模块:login.json
购物车模块:cart.json
订单模块:order.json

根据业务编写用例数据


测试数据参数化

修改测试脚本,使用 parameterized 实现参数化


日志收集

使用logging模块实现日志的收集


示例代码

import logging.handlers
import os
# 工程目录
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
​
def init_log_config():
  """
  初始化日志配置
  """
  # 日志输出格式
  fmt = "%(asctime)s %(levelname)s 
    [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
​
  # 创建日志器
  logger = logging.getLogger()
  logger.setLevel(logging.INFO)
  
  # 创建格式化器
  formatter = logging.Formatter(fmt)
  
  # 输出到控制台
  sh = logging.StreamHandler()
  sh.setFormatter(formatter)
  logger.addHandler(sh)
  
  # 输出到文件,每日一个文件
  log_path = os.path.join(BASE_DIR, "log", "tpshop.log")
  fh = logging.handlers.TimedRotatingFileHandler(log_path,  
    when='MIDNIGHT', interval=1
, backupCount=3)
  fh.setFormatter(formatter)
  logger.addHandler(fh)

生成测试报告

使用HTMLTestRunner生成测试报告

report_file = 
    "./report/report{}.html".format(time.strftime("%Y%m%d-%H%M%S"))
with open(report_file, "wb") as f:
runner = HTMLTestRunner(f, title="TPshop商城自动化测试报告", 
description="Win10.Firefox")
runner.run(suite)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值