Python 自动化测试实战—极简

Python 自动化测试实战

Selenium 基础知识

selenium 介绍及在工作中的应用:

Selenium 在工作中的应用常见于功能基本稳定、没有频繁大变动的网页。所以我们一般是在业务功能上线以后,为确保页面稳定,用 Selenium 实现自动化回归测试,结合 git、Jenkins 一起,每当有新功能上线时都会执行写好的 Selenium 代码以验证新上线的业务对原有页面功能没有造成影响。如有报错,则发送相应的通知,这样就可以确保对线上功能出现的未预期 bug 进行及时的修复。

安装Selenium:

pip3 install --upgrade pip
pip3 install selenium==3.14.0

配置Selenium环境: 

既然名为网页浏览器自动化自然要安装浏览器,一般来说,Chrome、Firefox等浏览器都可以,这里我们使用当前系统自带的Firefox作为实验浏览器。

通常,我们需要去下载浏览器对应的驱动,比如 Firefox 浏览器要下载 geckodriver 驱动,然后放到 /usr/local/bin文件夹中。当前环境中已经内置了驱动,了解步骤即可。

打开终端,将目录切换至桌面:

$ cd /home/shiyanlou/Desktop
下面我们来验证是否正常安装,在终端使用命令vim demo.py创建文件并写入代码:
#! /usr/bin/python3
from selenium import webdriver
driver = webdriver.Firefox()
driver.get("https://www.lanqiao.cn")

输入python3 demo.py如果浏览器打开并进入我们的网站,则环境配置就成功了。

此处输入图片的描述

浏览器操作

在终端使用命令vim demo2.py创建文件并写入代码:
#! /usr/bin/python3
from selenium import webdriver
from time import sleep
driver = webdriver.Firefox()

# 浏览器进入百度网站
driver.get("https://www.baidu.com")

# 设置浏览器宽800,高400
driver.set_window_size(800, 400)

# 等待3秒
sleep(3)

# 刷新页面
driver.refresh()

# 等待3秒
sleep(3)

# 最大化窗口
driver.maximize_window()
# 退出浏览器
driver.quit()
以上代码会在浏览器中执行:
  • 打开浏览器
  • 进入百度网站
  • 设置窗口大小为宽 800,高 400
  • 等待 3 秒
  • 刷新页面
  • 最大化窗口
  • 退出浏览器

说明:由于环境限制,当前浏览器不能实现后退操作,所以如果大家在本地搭建环境,可以增加:

  • 倒退页面
  • 前进页面
代码示例如下:
#! /usr/bin/python3

from selenium import webdriver
from time import sleep


driver = webdriver.Firefox()

# 浏览器进入百度网站
driver.get("https://www.baidu.com")

# 设置浏览器宽800,高400
driver.set_window_size(800, 400)

# 等待3秒
sleep(3)

# 最大化窗口
driver.maximize_window()
# 进入另一个网站
driver.get("https://www.lanqiao.cn/")
sleep(3)

# 后退到上一个页面--百度网站
driver.back()

sleep(3)

# 前进到下一个页面--实验楼网站
driver.forward()

sleep(3)

# 退出浏览器
driver.quit()

定位元素

webdriver 提供了一系列的元素定位方法,常用的有以下几种:

  • id
  • name
  • class name
  • tag name
  • link text
  • partial link text
  • xpath
  • css selector

分别对应 python webdriver 中的方法为:

  • find_element_by_id()
  • find_element_by_name()
  • find_element_by_class_name()
  • find_element_by_tag_name()
  • find_element_by_link_text()
  • find_element_by_partial_link_text()
  • find_element_by_xpath()
  • find_element_by_css_selector()

2.5.1 点击定位到的元素

  • click()

使用方法:一般为先进行元素的定位,如果该元素可以点击如:超链接、文本框、带有超链接的图片等,则该元素可以进行点击操作:find_element_by_xxx().click()。

2.5.2 清空文本框、向文本框输入内容

  • 清空:clear()
  • 输入:.send_keys("输入的内容")

使用方法:无论是清空还是输入操作,都是先进行元素即文本框的定位,然后调用对应的方法,即:清空find_element_by_xx().clear(),输入find_element_by_xx().send_keys("输入的内容")

2.5.3 获取元素属性

  • 文本信息:.text

使用方法:定位到对应元素以后,直接调用方法:find_element_by_xx().text,注意:text 后面不要加括号

  • 元素尺寸:.size

使用方法:同上

  • 其他属性:.get_attribute("想获取的属性名")

使用方法:定位到对应元素以后,调用.get_attribute("属性名")方法,传值为想获取的属性名。注:查看元素的各个属性可通过 Chrome 自带的开发者工具,快捷键为F12,通过元素查看器定位到想查看的元素,然后在开发者工具中查看具体的属性名,如class、type、id等。

这里我们使用51Testing 软件测试论坛作为演示网站,如果大家没有账号需要先去注册一个,下面的代码将会使用到账号信息,在终端使用命令vim demo3.py创建文件并写入代码:
#! /usr/bin/python3

from selenium import webdriver
from time import sleep


driver = webdriver.Firefox()

# 进入51testing网站
driver.get("http://bbs.51testing.com/forum.php")
sleep(3)

# 用id定位账号输入框并输入账号
driver.find_element_by_id("ls_username").send_keys("您的用户名")

# 用id定位密码输入框并输入密码
driver.find_element_by_id("ls_password").send_keys("密码")

# 定位“登录”按钮并获取登录按钮的文本
txt = driver.find_element_by_xpath('//*[@id="lsform"]/div/div[1]/table/tbody/tr[2]/td[3]/button').text

# 打印获取的文本
print(txt)

# 定位“登录”按钮并获取登录按钮的type属性值
type = driver.find_element_by_xpath('//*[@id="lsform"]/div/div[1]/table/tbody/tr[2]/td[3]/button').get_attribute("type")

# 打印type属性值
print(type)

# 定位“登录”按钮并进行点击操作
driver.find_element_by_xpath('//*[@id="lsform"]/div/div[1]/table/tbody/tr[2]/td[3]/button').click()

在终端执行python3 demo3.py运行,结果显示如下:

页面显示:

执行以上代码后会在 xfce 中输出如下信息:

此处输入图片的描述

下拉页面

说明:下拉页面需要用 js 命令

  • 下拉指定高度
js = 'document.documentElement.scrollTop=具体的下拉高度值;'
driver.execute_script(js)

解释:js = 'document.documentElement.scrollTop=具体的下拉高度值;'为 js 语句,意为下拉页面滚动条;driver.execute_script(js)为 python 代码,意为执行上面的 js 语句。

  • 用目标元素做参考下拉页面
target_element = driver.find_element_by_xx()
js = 'arguments[0].scrollIntoView();'
driver.execute_script(js,target_element)

解释:target_element = driver.find_element_by_xx()先对目标元素进行定位;js = 'arguments[0].scrollIntoView();'js 下拉命令;driver.execute_script(js, target_element)python 代码,执行脚本,传两个参数,第一个是 js 命令,第二个是目标元素。

在终端使用命令vim demo4.py创建文件并写入代码:
#! /usr/bin/python3

from selenium import webdriver
from time import sleep

driver = webdriver.Firefox()
driver.get("http://bbs.51testing.com/forum.php")
sleep(3)

# 页面下拉指定高度
js = 'document.documentElement.scrollTop=800;'
driver.execute_script(js)

在终端执行python3 demo4.py运行,页面在等待 3 秒后会出现下拉行为。

页面弹窗 alert 的定位

如果页面有alert形式的提醒框,则用以下语句

  • driver.switch_to.alert
alert = driver.switch_to.alert

# 查看alert中的文字

print(alert.text)

# 点击确定

alert.accept()

# 点击取消(如果有)

alert.dismiss()

切换窗口

  • .switch_to.window()
说明:很多时候我们点击按钮以后会新开页面,这时候要根据页面的句柄来切换窗口,获取所有页面句柄方法为.window_handles,而获取当前页面的句柄语法则为.current_window_handle,现在我们假设页面开了两个窗口,那么如何在两个窗口之间进行切换呢?很简单,就是用一个for循环即可,如果循环到的句柄与当前句柄不一致,那么就切换句柄:
# 获取窗口所有句柄
all_handles = driver.window_handles
# 获取当前窗口句柄
curr_window = driver.current_window_handle
# 遍历所有句柄
for k in all_handles:
    # 如果不是当前窗口句柄
    if k != curr_window:
        # 窗口句柄切换
        driver.switch_to.window(k)

定位 iframe

  • .switch_to.frame():切换到 iframe
  • .switch_to.default_content(): 切换出 iframe

说明:iframe 经常在账号、密码输入框、发帖内容编辑框处出现,一般我们需要先通过开发人员工具确定该输入框是否是 iframe,如果是,则需要先定位 iframe。对 iframe 定位,一般需要先通过 xpath 定位到 iframe 的位置,然后通过.switch_to.frame()方法切换到 iframe 中,iframe 就像一个盒子,我们进入了盒子内部,进行预期的操作,然后需要跳出盒子才能继续对页面元素进行操作,所以执行完 iframe 内的操作后需要跳出 iframe 可以通过.switch_to.default_content()方法。 driver.switch_to.default_content()


iframe = driver.find_element_by_xpath()

# 切换到iframe

driver.switch_to.frame(iframe)

...页面操作代码...

# 跳出iframe

driver.switch_to.default_content()


Xpath 知识与实战

本节课程介绍如何通过 Firefox 浏览器插件获取元素的 xpath 以及在特殊情况下如何自己写 xpath。

xpath 介绍

xpath 使用路径表达式在 XML 文档中进行导航。也就是说,当我们用 xpath 进行定位的时候,代码会根据你写的 xpath 一层一层的进行定位。xpath 就像是一个地图,指引代码找到它的目标元素。

利用 Firefox 浏览器插件定位 xpath

点击屏幕左下方的所有应用程序>互联网>Firefox 网络浏览器就可以打开 Firefox 浏览器,然后进入实验楼 (www.shiyanlou.com) 网站。现在要对“路径”按钮进行定位:

点击键盘的 F12,这时浏览器会弹出开发者工具:

最左侧的方框+小箭头叫做元素选择器。单击这个元素选择器以后,去页面点击想定位的元素——“路径”:

此时定位到的标签,就是“路径”按钮对应的 HTML 标签了。然后将光标移动至该标签处,单击鼠标右键,选择复制,可以看到,这里可以选择复制 css 路径(即 css_selector),也可以选择复制 XPath。

这里我们选择 XPath,然后在文本编辑器里就可以粘贴复制的 XPath 了:

//*[@id="header-navbar-collapses"]/ul[1]/li[3]/a

XPath 介绍

表达式 描述
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
* 匹配任何元素节点

根据上面的表格,我们分析一下这个 xpath://*[@id="header-navbar-collapses"]/ul[1]/li[3]/a

  • //即从当前选择的文档节点开始
  • *匹配任何元素节点
  • [@id="header-navbar-collapses"]通过id属性确认当前文档开始的节点位置,即从id为header-navbar-collapses的位置开始
  • /这个斜杠没有打头,所以这里意思是下一级
  • ul[1]这里方括号中的1表示第一个ul标签。注意:xpath 中的标签数从 1 开始
  • li[3]表示第三个li标签

再举一个例子

现在来看“登录”的 xpath://*[@id="header-navbar-collapses"]/ul[2]/li[2]/a

  • //即从当前选择的文档节点开始
  • *匹配任何元素节点
  • [@id="header-navbar-collapses"]通过id属性确认当前文档开始的节点位置
  • /斜杠没有用作开头,所以这里是下一级的意思
  • ul[2]定位到上面id属性下一层的第二个ul标签
  • li[2]定位到ul下一层的第二个li标签
  • a定位到li标签下一层的a标签                                                                                       

手写XPath

有时候我们用复制来的 xpath 并不能定位到我们想要的元素,原因在于有一些网站的id元素属性也是变化的。这时候我们就只能自己来写 xpath 了。

2.4.1 例子一

我们来定位“实验楼”这个图片元素的 xpath,看到的 HTML 文档结构如下图:

分析:

  • 目标元素是img标签
  • 从img标签往上查找文档,看有没有唯一的属性值,如id。注意,class属性值一般不是唯一的,所以一般不用class属性作为定位元素
  • 发现文档中直到文档顶部也没有属性唯一的元素,所以这里我们就从文档顶部即根节点开始
  • /的意思即从根节点选取
  • 所以我们可以确定 xpath 开头是/
  • 第一层是html标签,所以 xpath 初步确定为/html
  • 第二层是body标签,xpath 确定为/html/body
  • 目标元素在body标签下第三个div中,所以 xpath 确定为/html/body/div[3]
  • 下一层只有一个div标签,这是可以省略后面的[1],即/html/body/div[3]/div
  • 再下一层在第二个div中,所以 xpath 确定为/html/body/div[3]/div/div[2]
  • 依次类推,继续向下一层定位,即可确定 xpath 为/html/body/div[3]/div/div[2]/div/nav/div[1]/a/img

2.4.2 例子二

我们来定位搜索框的 xpath,看到的 HTML 文档结构如下图:

分析:

  • 目标元素是a标签
  • 从目标元素开始往上查找属性唯一的元素
  • 发现有id属性,且该属性值固定不变,所以可以直接利用id属性进行定位
  • //即从当前选择的文档节点开始,即//*[@id='header-navbar-collapses']
  • 下一层ul不唯一,所以 xpath 里要写索引,即//*[@id="header-navbar-collapses"]/ul[1]
  • 接下来的li标签也不是唯一的,所以 xpath 确定为//*[@id="header-navbar-collapses"]/ul[1]/li[1]
  • 最后定位到a标签,所以确定 xpath 为//*[@id="header-navbar-collapses"]/ul[1]/li[1]/a

自动化测试用例与网易邮箱登录实战

 对于测试人员来说,完善的测试用例是避免漏测的关键。对任何模块测试前编写对应的测试用例可以提高测试点覆盖率、测试效率,也可以在出现漏测的情况时进行回查反思,避免接下来的工作中再次出现类似情况。

测试方法

常用的测试方法包括:等价类、边界值、正交排列、因果图、场景法。

  • 等价类
  1. 适用场合:有数据输入的地方,可以使用等价类划分,将大量的数据划分出若干范围,从每个范围中挑选代表数据进行测试,避免穷举,提高测试效率。
  1. 等价类方法划分:有效等价类,无效等价类 有效等价类:输入有意义,合理的数据集合; 无效等价类:输入无意义的,不合理的数据集合;
  2. 等价类划分法使用步骤:
  • 分析需求划分等价类(分为初步划分和细化);
  • 将等价类填写到<<等价类表>>中;
  • 从每个等价类中至少挑选一个代表数据,编写测试用例,执行测试。
  • 边界值
  1. 适用场合:常用于数据输入的地方,一般作为等价类划分的补充,和等价类划分一起使用。
  2. 使用步骤:找到有效数据和无效数据之间的分界点,对分界点及其两边的点进行测试。
使用等价类+边界值测试的思路
  • 1.先对有效数据进行测试- 1 个测试用例尽可能的将多个控件的有效数据组合起来测(优化)
  • 2.再对无效数据进行测试- 无效数据需要单独测试(为了避免屏蔽现象)
  • 3.最后对多个无效数据组合测试(适当强化)
  • 因果图
  1. 适用场合:界面中考虑控件的组合和限制关系的情况(组合数量较少)。
  2. 因果图中常用的 9 个图形符号:恒等,与,或,非;互斥(E),唯一(O),包含(I),要求(R),屏蔽(M);
  3. 使用步骤:
+ 找出所有的输入条件(因),和所有的输出结果(果); + 找出输入条件的所有组合和限制; + 每组输入条件组合对应的输出结果,画因果图,填判定表(画因果图可以省略); + 编写测试用例,每一列对应一条测试用例。
  • 场景法
  1. 适用场合:当需要测试软件的业务流程(逻辑)时,适合用场景法,场景法是基于业务的方法,有测试人员模拟用户在使用软件的各种不同的情况;
  1. 场景法划分:基本流和备选流 > 基本流:也叫有效流或正确流,模拟用户正确的操作的过程; 备选流:也叫无效流或错误流,模拟用户错误的操作的情景;
  1. 场景法的使用步骤:
+ 分析需求,整理业务流程(逻辑),列出场景; + 根据列出场景填写场景表; + 为每个场景编写适当的测试用例(不一定是1:1的)。
  • 正交排列
  1. 适用场合:对于参数配置类软件,以及兼容性测试时需要考虑各个控件之间的组合情况(组合较多),使用正交排列法选择较少的组合达到最佳的测试效果。
  2. 使用步骤:
  • 分析需求,列出参与组合的控件以及每个控件的取值;
  • 选择合适的正交表(确定 m = 控件取值个数, k = 控件数);
  • 完成控件,控件取值对应因子和状态的映射;
  • 编写测试用例。

自动化测试用例设计规范

自动化测试的用例与功能测试的用例不同,功能测试的测试用例讲究全面覆盖,也就是说如果穷举法可以覆盖所有测试点的话那么穷举就是功能测试最好的方法。但是自动化测试用例则不同,总结起来自动化测试用例有如下特点:

  • 少而精
  • 用例健壮
  • 必有预期结果——断言

解释:

  1. 少而精。即写少量的用例覆盖大量的测试点。比如对提示语、点击按钮后的页面响应等可以用一个用例覆盖——这就要求我们在预期结果中对所有的判断进行说明。
  2. 用例健壮。我们知道,自动化测试主要是由代码实现的,我们在用代码实现测试点覆盖以后这个测试代码将会经常被使用。而我们的业务也在不断更新,试想如果每次更新业务都需要对自动化代码进行维护,那可能维护代码的时间反而与功能测试占用时间相差无几。所以自动化测试代码要讲究健壮性,即新功能上线不会对原有代码(自动化测试)造成大的影响。就像我们写代码时所讲的”面向对象“的思想——我们要把固定的模块单独作为测试用例,减少用例耦合,这样在某个模块有大的变动时就不会造成整体用例代码的修改了。
  3. 必有预期结果——断言。功能测试有预期结果进行验证,而在自动化测试中则称为断言。也就是代码中所有涉及到做了这个操作以后会……的场景都要添加断言,而在代码中断言不一定要用assert,如果必要的情况用if判断也同样可以。在代码中讲究的是灵活,而不是无脑的遵循代码的写法。
所以我们在设计自动化测试用例时既要保证用例的以上特点,又要熟练运用常用的测试方法,比如等价类、边界值等,以此来完善我们的测试用例。

网易邮箱登录实战

2.3.1 测试用例

用例编号 测试点 执行步骤 预期结果
1 登录成功 1. 输入用户账号
2. 输入用户密码
3.点击登录按钮
1. 登录框默认文字”邮箱帐号或手机号“
2. 密码框默认文字”密码“
3. 登录成功
4. 登录成功后显示用户名

2.3.2 分析

  • 准备
先进入网易邮箱https://mail.163.com,打开开发者选项,查看网页的代码文档。因为我们要对输入框进行定位,所以直接通过元素选择器选择登录框:
frame = driver.find_element_by_xpath('//*[@id="loginDiv"]/iframe')
# 切换到iframe
driver.switch_to.frame(frame)
  • 输入账号

其次我们发现账号框input层的id是变化的,用浏览器工具得到的 xpath 是含有id的,这样就无法定位到输入框,这时就需要我们自己写 xpath,如下图,我们看到div层的id是固定值,所以我们可以从固定id层开始定位。最终得到的xpath就可以是//*[@id="account-box"]/div[2]/input。

  • 输入密码

对密码框进行定位时账号框的id就不能再用了,因为账号和密码框不属于同一个div。所以我们只能继续往上找,就会发现form标签的id也是固定的。利用之前学过的xpath方法,可以获得密码框的xpath为//*[@id='login-form']/div/div[3]/div[2]/input[2]。

  • 点击登录按钮
因为登录按钮同样在这个iframe中,所以此时我们不必切换出iframe即可对登录按钮进行点击。而登录按钮可以直接通过id进行定位得到。所以该点击操作只需要:
driver.find_element_by_xpath('//*[@id="dologin"]').click()
进行完以上操作以后,页面已经完成登录,但不要忘记切换出iframe,即:
# 切换出iframe
driver.switch_to.default_content()

2.3.3 代码

下面我们就写代码测试网易邮箱的登录功能,大家如果没有网易邮箱的可以先注册一个,代码中将会使用到你的账号信息。

使用vim /home/shiyanlou/Desktop/test.py打开文件并写入如下代码内容:
#! /usr/bin/python3

from selenium import webdriver
from time import sleep


driver = webdriver.Firefox()
driver.get("https://mail.163.com/")
sleep(3)
frame = driver.find_element_by_xpath('//*[@id="loginDiv"]/iframe')
# 切换到iframe
driver.switch_to.frame(frame)
account = driver.find_element_by_xpath('//*[@id="account-box"]/div[2]/input').get_attribute("data-placeholder")
print(account)
assert account == '邮箱帐号或手机号码'
driver.find_element_by_xpath('//*[@id="account-box"]/div[2]/input').send_keys("您的账号")
passwd = driver.find_element_by_xpath("//*[@id='login-form']/div/div[3]/div[2]/input[2]").get_attribute("data-placeholder")
print(passwd)
assert passwd == '输入密码'
driver.find_element_by_xpath("//*[@id='login-form']/div/div[3]/div[2]/input[2]").send_keys("您的密码")
driver.find_element_by_xpath('//*[@id="dologin"]').click()
# 切换出iframe
driver.switch_to.default_content()
sleep(3)
name = driver.find_element_by_xpath("//*[@id='dvContainer']/div/div/div[2]/div/div[2]/span/span").text
print(name)
assert name == '您的用户名'

以上操作流程为:切入iframe -> 定位账号输入框 -> 获取账号输入框默认文字 -> 断言文字内容 -> 输入账号 -> 定位密码输入框 -> 获取密码输入框默认文字 -> 断言文字内容 -> 输入密码 -> 点击登录按钮 -> 切出iframe -> 获取用户名 -> 断言用户名。

unittest单元总结

本节课程介绍自动化用例在集成时用到的单元测试库 unittest。
当我们能够把 Selenium 测试脚本完成以后,就需要把整体的代码进行封装形成框架,这样在我们执行的时候就会方便很多,也就是说我们不必一个一个的去执行测试脚本,而是执行一个文件即可运行所有的脚本代码。这样不仅方便执行,而且对于后续的持续集成将代码部署到jenkins上也会非常方便。此外,我们每次自动化测试执行完成以后都需要发送邮件形式的测试报告,这里我们将利用HTMLTestRunner生成测试报告。

unittest 单元测试介绍

首先导入unittest单元测试库

import unittest
我们可以通过python自带的help方法查看官方文档
shiyanlou:Desktop/ $ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits"&nbs***bsp;"license" for more information.
>>> import unittest
>>> print(help(unittest))
可以看到打印的内容中既有代码案例,也有对应的网址链接:
Help on package unittest:

NAME
    unittest

MODULE REFERENCE
    https://docs.python.org/3.6/library/unittest
...

DESCRIPTION
    Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
    Smalltalk testing framework (used with permission).

...

    Simple usage:

        import unittest

        class IntegerArithmeticTestCase(unittest.TestCase):
            def testAdd(self):  # test method names begin with 'test'
                self.assertEqual((1 + 2), 3)
                self.assertEqual(0 + 1, 1)
            def testMultiply(self):
                self.assertEqual((0 * 10), 0)
                self.assertEqual((5 * 8), 40)

        if __name__ == '__main__':
            unittest.main()

    Further information is available in the bundled documentation, and from

      http://docs.python.org/library/unittest.html
我们将案例中的代码拿出来进行分析:
#! /usr/bin/python3

import unittest

class IntegerArithmeticTestCase(unittest.TestCase):
    def testAdd(self):  # test method names begin with 'test'
        self.assertEqual((1 + 2), 3)
        self.assertEqual(0 + 1, 1)
    def testMultiply(self):
        self.assertEqual((0 * 10), 0)
        self.assertEqual((5 * 8), 40)

if __name__ == '__main__':
    unittest.main()

定义类名的时候类需要继承unittest.TestCase类。定义类中的函数名时以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的,比如上面代码中的assertEqual()即判断是否相等。

我们将该文件,放在桌面上,命名为IntegerArithmeticTestCase.py,然后执行该文件:

上面testAdd()函数与testMultiply()函数的两个断言都是正向的,现在我们将上面testMultiply()函数中的self.assertEqual((5 * 8), 40)改为self.assertEqual((5 * 8), 41),即该断言是无法通过的。重新执行文件,可以看到:

执行结果为.F即第一个函数通过,第二个执行出错,并且下面给出了报错的原因AssertionError:40 != 41。

如果觉得只是.不能确认通过的函数名,可以通过修改verbosity参数值来进行查看,我们继续修改上面的代码
if __name__ == '__main__':
    unittest.main(verbosity=2)

然后执行该文件:

可以看到,上面执行结果中显示了对应的函数名字以及执行的结果。

写脚本分析 unittest 的一些问题

现在我们自己写一个脚本,来分析关于unittest的一些问题。
#! /usr/bin/python3

import unittest

class MyTestUnittest(unittest.TestCase):

    def test01(self):
        print("This is test01")

    def test03(self):
        print("This is test03")

    def test02(self):
        print("This is test02")

if __name__ == '__main__':
    unittest.main(verbosity=2)

将上面代码保存为Desktop/MyTestUnittest.py文件并保存到桌面,然后我们执行这个文件:

注意,在上面代码中我们是按照test01->test03->test02顺序写的,但是执行的时候是按照test01->test02->test03的顺序执行的,因此,通过这个例子我们知道所有的test...方***按照阿拉伯数字顺序执行。

接下来我们将代码改为:
#! /usr/bin/python3

import unittest

class MyTestUnittest(unittest.TestCase):

    def testAOne(self):
        print("This is test01")

    def testCThree(self):
        print("This is testCThree")

    def testBTwo(self):
        print("This is testBTwo")

    def FourTest(self):
        print("This is FourTest")

if __name__ == '__main__':
    unittest.main(verbosity=2)

执行后看到:

可以看到,正如前文所述文件执行了 3 个以test开头的方法,最后面的FourTest并没有执行。而且,执行的顺序是按照test后面的字母顺序执行的。

综上所述,我们在写test...用例的时候就可以通过阿拉伯数字或者首字母来控制用例的执行顺序了。

setUp 与 tearDown

unittest库还提供了前置条件和后置条件的设置,即setUp()和tearDown()方法。修改MyTestUnittest文件代码如下:
#! /usr/bin/python3

import unittest

class MyTestUnittest(unittest.TestCase):

    def setUp(self):
        print("This is setUp method")

    def testAOne(self):
        print("This is test01")

    def testCThree(self):
        print("This is testCThree")

    def testBTwo(self):
        print("This is testBTwo")

    def FourTest(self):
        print("This is FourTest")

    def tearDown(self):
        print("This is tearDown method")

if __name__ == '__main__':
    unittest.main(verbosity=2)

执行文件:

可以看到,每次执行test...以前都会执行一次setUp方法,执行完test…以后也会相应的执行tearDown方法。

setUpClass 与 tearDownClass

我们在做Selenium自动化测试的时候,需要打开浏览器操作,这时候其实打开浏览器这个操作就是所谓的前置条件关闭浏览器就是后置条件。但是如果用setUptearDown方法,那么每执行一个用例浏览器就会进行一次打开和关闭的操作,这样对我们来说不够效率。所以前置条件后置条件还可以通过setUpClasstearDownClass方法来操作:
#! /usr/bin/python3

import unittest

class MyTestUnittest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("This is setUpClass method")

    def testAOne(self):
        print("This is test01")

    def testCThree(self):
        print("This is testCThree")

    def testBTwo(self):
        print("This is testBTwo")

    def FourTest(self):
        print("This is FourTest")

    @classmethod
    def tearDownClass(cls):
        print("This is tearDownClass method")

if __name__ == '__main__':
    unittest.main(verbosity=2)

执行文件:

注意到,setUpClass与tearDownClass只在test…执行以前和全部执行结束以后分别执行了一次。

跳过测试用例

在testCThree方法上面增加跳过命令
#! /usr/bin/python3

import unittest

class MyTestUnittest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("This is setUpClass method")

    def testAOne(self):
        print("This is test01")

    @unittest.skip("skip testCThree")
    def testCThree(self):
        print("This is testCThree")

    def testBTwo(self):
        print("This is testBTwo")

    def FourTest(self):
        print("This is FourTest")

    @classmethod
    def tearDownClass(cls):
        print("This is tearDownClass method")

if __name__ == '__main__':
    unittest.main(verbosity=2)
执行文件:

如上图,testCThree被跳过执行了。

批量执行

1.先在桌面新建文件夹MySelenium,在该文件夹中新建runTests.py文件。然后在该文件夹中再次新建testCases文件夹:
$ cd /home/shiyanlou/Desktop
$ mkdir MySelenium && cd MySelenium
$ touch runTests.py && mkdir testCases

2.在testCases文件夹中新建空的__init__.py文件(有了这个文件,该文件夹就会被识别为包),并新建testMyTestUnittest.py文件和testIntegerArithmeticTestCase.py文件。

代码分别为:

testMyTestUnittest.py文件代码:
#! /usr/bin/python3

import unittest

class MyTestUnittest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("This is setUpClass method")

    def testAOne(self):
        print("This is test01")

    @unittest.skip("skip testCThree")
    def testCThree(self):
        print("This is testCThree")

    def testBTwo(self):
        print("This is testBTwo")

    def FourTest(self):
        print("This is FourTest")

    @classmethod
    def tearDownClass(cls):
        print("This is tearDownClass method")

if __name__ == '__main__':
    unittest.main(verbosity=2)
testIntegerArithmeticTestCase.py文件代码:
#! /usr/bin/python3

import unittest

class testIntegerArithmeticTestCase(unittest.TestCase):
    def testAdd(self):  # test method names begin with 'test'
        self.assertEqual((1 + 2), 3)
        self.assertEqual(0 + 1, 1)
        print("testAdd passed")
    def testMultiply(self):
        self.assertEqual((0 * 10), 0)
        self.assertEqual((5 * 8), 40)
        print("testMultiply passed")

if __name__ == '__main__':
    unittest.main()
现在在runTests.py文件中输入如下代码:
#! /usr/bin/python3

import unittest

discover = unittest.defaultTestLoader.discover("/home/shiyanlou/Desktop/MySelenium/testCases/", pattern="test*.py")
print(discover)

解释:

unittest.defaultTestLoader.discover("/home/shiyanlou/Desktop/MySelenium/testCases/", pattern="test*.py")所传的第一个参数即测试用例所在位置,第二个参数为用例规则,即所有要执行的用例文件都需要以test开头。如果有其它不是以test开头命名的文件,则可以在pattern参数中传对应的开头字母。

在xfce中切换到MySelenium目录,然后执行runTests.py文件

可以看到,打印的discover即代码找到的两个文件中的所有以test开头的方法。

继续修改runTests.py代码:
#! /usr/bin/python3

import unittest

discover = unittest.defaultTestLoader.discover("/home/shiyanlou/Desktop/MySelenium/testCases/", pattern="test*.py")
print(discover)
runner = unittest.TextTestRunner()
runner.run(discover)

执行:

可以看到,所有以test开头的方法都执行并打印了对应的内容。包括用例总数、跳过的用例数。

利用 unittest 对自动化测试用例进行集成

本节实验将前面讲的所有内容利用 unittest 单元测试库以及简单的面向对象的思想进行封装,使代码形成框架。
对于自动化测试人员来说,每执行完一次测试用例都要生成相应的测试报告方便查看测试结果,因此我们使用 HTMLTestRunner 生成测试报告。

 使用 HTMLTestRunner 生成测试报告

下载 HTMLTestRunner.py

  • 下载地址:
$ wget https://labfile.oss-internal.aliyuncs.com/courses/1163/HTMLTestRunner.py
  • 将下载好的文件移至python3的文件夹下:
$ sudo mv HTMLTestRunner.py /usr/lib/python3/dist-packages/

然后打开之前写好的MySelenium/runTests.py文件。添加代码:

from HTMLTestRunner import HTMLTestRunner

保存,然后执行该文件,如果没有报错,证明导入成功。

然后我们利用HTMLTestRunner来生成测试报告。

先在MySelenium文件夹下新建文件夹,命名为report

$ mkdir /home/shiyanlou/Desktop/MySelenium/report
然后将runTests.py文件代码改为:
#! /usr/bin/python3

import unittest
from HTMLTestRunner import HTMLTestRunner

discover = unittest.defaultTestLoader.discover("/home/shiyanlou/Desktop/MySelenium/testCases/", pattern="test*.py")
filename = "/home/shiyanlou/Desktop/MySelenium/report/report.html"
fp = open(filename, 'wb')
runner = HTMLTestRunner(stream=fp, title='AutoTest', description='My Selenium auto test')
runner.run(discover)
fp.close()

解释:

filename为测试报告存放的地址,以及保存的文件名。 runner = HTMLTestRunner(stream=fp, title='AutoTest', description='My Selenium auto test'),其中stream为选择打开的文件,title为测试报告的标题,description为测试报告的描述。

用系统自带的python3执行该文件:

执行完成以后,可以进入report文件夹,会看到文件夹中自动生成了report.html的文件,双击打开,可以看到:

到此为止,我们便利用HTMLTestRunner生成了测试报告。

将代码封装形成框架

  • 封装思想

代码封装应用的是面向对象的思想,即将公共方法提取出来,用到的时候采用调用的方式。这样做的好处有四点:

i. 减少代码冗余

ii. 减少代码耦合

iii. 方便定位代码报错

iv. 方便代码维护

  • 框架结构
其中:
  • baseInfo文件夹存放用例用到的各种参数,如用户名、密码等
  • modules文件夹存放各种固定方法,如发送邮件、获取测试用例、读取测试结果等
  • report文件夹存放生成的测试报告
  • testCases文件夹存放测试用例
首先,我们需要获取各个文件夹的目录,在 python 中os库可以读取当前文件夹的路径,所以,我们在modules文件夹新建读取文件目录的文件dir_path.py:
#! /usr/bin/python3

from os import path

def dir_path():
    file_path = path.dirname(__file__)
    # 返回父级目录
    return path.dirname(file_path)
我们生成了测试报告以后需要以邮件的方式发送,所以我们需要写一个发送邮件的方法。这里需要注意,为了方便起见,邮件的标题最好就是测试的结果,所以我们把标题内容以参数的方式传递。
我们新建modules/sendEmail.py文件,并写入如下代码:
#! /usr/bin/python3

import smtplib
import baseInfo
import time
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.text import MIMEText

def send_Mail(testReport, result):
    f = open(testReport, 'rb')
    # 读取测试报告正文
    mail_body = f.read()
    f.close()
    try:
        smtp = smtplib.SMTP(baseInfo.mail_server, 25)
        sender = baseInfo.mail_sender
        password = baseInfo.mail_password
        receiver = baseInfo.mail_receiver
        smtp.login(sender, password)
        msg = MIMEMultipart()
        text = MIMEText(mail_body, 'html', 'utf-8')
        msg.attach(text)
        msg['Subject'] = Header('[自动化测试结果:'+result+']', 'utf-8')
        msg['From'] = sender
        msg['To'] = ','.join(receiver)
        msg_file = MIMEText(mail_body, 'html', 'utf-8')
        msg_file['Content-Type'] = 'application/octet-stream'
        msg_file['Content-Disposition'] = 'attachment; filename="report.html"'
        msg.attach(msg_file)
        smtp.sendmail(sender, receiver, msg.as_string())
        smtp.quit()
        return True
    except smtplib.SMTPException as e:
        print(str(e))
        return False
那么标题内容如何获取呢?打开你的测试报告:

可以看到有一个测试结果,那么我们就可以应用Selenium获取元素的方式将这个测试结果取出,作为result参数传递给发送邮件的方法.

我们新建modules/getTestResult.py文件,并写入如下代码:
#! /usr/bin/python3

from selenium import webdriver
from time import sleep


def get_results(filename):
    driver = webdriver.Firefox()
    driver.maximize_window()
    result_url = "file://%s" % filename
    driver.get(result_url)
    sleep(3)
    res = driver.find_element_by_xpath("/html/body/div[1]/p[4]").text
    result = res.split(':')
    driver.quit()
    return result[-1]
新建baseInfo/__init__.py文件,内容为(注释是以 QQ 邮箱为例):
#! /usr/bin/python3

'''
发送邮件参数
'''

mail_server = '使用的邮箱服务器'  # smtp.qq.com
mail_sender = '发送邮件的邮箱'   # xxxxxxxxxx@qq.com
mail_password = '邮箱授权码'  # 到QQ邮箱中获取邮箱授权码
mail_receiver = ['邮件接收人邮箱']  # 收件邮箱可以和发件邮箱相同
我这里使用的是 QQ 邮箱,提供一下具体的操作方法,其它的邮箱操作类似。登录 QQ 邮箱,点击左上角的设置>账户>POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,然后开启POP3/SMTP服务和IMAP/SMTP服务,最后点击下面的生成授权码来获得授权码。

然后回到runTests.py文件。将对应方法以调用的形式获取:
#! /usr/bin/python3


from HTMLTestRunner import HTMLTestRunner
from modules import sendEmail
from modules import getTestResult
from modules import dir_path
import unittest


if __name__ == '__main__':

    # 测试用例路径
    test_dir = dir_path.dir_path() + '/testCases'
    # 测试报告存放路径
    report_dir = dir_path.dir_path() + '/report'

    filename = report_dir + '/report.html'
    fp = open(filename, 'wb')
    runner = HTMLTestRunner(stream=fp, title='自动化测试', description='用例执行结果')
    discover = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')
    runner.run(discover)
    fp.close()
    result = getTestResult.get_results(filename)
    mail = sendEmail.send_Mail(filename, result)
    if mail:
        print(u'邮件发送成功!')
    else:
        print(u'邮件发送失败!')

完成后执行python3 runTests.py文件:

可以看到控制台打印了邮件发送成功!。

然后来到你接收的邮箱:

可以看到邮件接收成功,并且邮件标题即测试结果。

数据驱动

本节课程介绍使用数据驱动维护测试用例数据,将测试数据与代码分开存放。
对于自动化测试用例来说,有很多用例数据需要经常维护,如果把所有用例数据都放在代码里,那么在维护的时候会非常麻烦,解决方式之一就是将用例数据放到一个文件中进行统一维护。这就涉及到了数据驱动的知识。

安装并测试

首先需要安装数据驱动 ddt 库,打开xfce输入

$ pip3 install --user ddt

如上图,这样就成功安装了ddt库。

在 Desktop/MyTest.py 文件中添加如下代码:
#! /usr/bin/python3

import ddt
import unittest

data = [{"username": "Ray", "pwd": "123456"},
     {"username": "Stephen", "pwd": "abcdefg"},
     {"username": "Kobe", "pwd": "abc123"}]

@ddt.ddt
class MyTest(unittest.TestCase):
    def setUp(self):
        pass
    def tearDown(self):
        pass

    @ddt.data(*data)
    def test01(self, testdata):
        print(testdata)


if __name__ == '__main__':
    unittest.main()    
代码中data为用例数据。在用ddt库时需要先使用装饰器@ddt.ddt,在用例引用装饰器,导入测试数据。执行文件:

可以看到用例数据被打印出来了。

查看 ddt 的数据读取方式

为了更加清晰ddt数据的读取方式,我们现在将代码修改为:
#! /usr/bin/python3

import ddt
import unittest

data = [{"username": "Ray", "pwd": "123456"},
     {"username": "Stephen", "pwd": "abcdefg"},
     {"username": "Kobe", "pwd": "abc123"}]

@ddt.ddt
class MyTest(unittest.TestCase):
    def setUp(self):
        print("setUp")
    def tearDown(self):
        print("tearDown")

    @ddt.data(*data)
    def test01(self, testdata):
        # print("start")
        print(testdata)
        # print("end")


if __name__ == '__main__':
    unittest.main()

然后重新执行代码:

从执行结果我们可以看到,三条数据对应下面显示的三条测试用例。且每读取一次数据,都会先执行setUp方法,一条数据读取结束后会相应的执行tearDown方法。所以我们就可以根据ddt读取数据的特点来设计对应的测试用例了。

将数据提取为文件

我们还可以将数据像上一个实验一样单独提取成文件的形式,在应用的时候采用导入的方法。 新建Desktop/baseinfo文件夹,并在文件夹中新建__init__.py文件,向文件中添加数据如下:data = [{"username": "Ray", "pwd": "123456"}, {"username": "Stephen", "pwd": "abcdefg"}, {"username": "Kobe", "pwd": "abc123"}] 然后修改Desktop/MyTest.py文件内容如下:
#! /usr/bin/python3

import ddt
import unittest
# baseinfo包中的__init__.py文件为存放数据文件
import baseinfo


@ddt.ddt
class MyTest(unittest.TestCase):
    def setUp(self):
        print("setUp")
    def tearDown(self):
        print("tearDown")
    # 数据提取采用调用方法的方式
    @ddt.data(*baseinfo.data)
    def test01(self, testdata):
        # print("start")
        print(testdata)
        # print("end")


if __name__ == '__main__':
    unittest.main()

使用python3 MyTest.py执行文件,结果和上面相同:

当然,大家也可以通过 Excel 进行数据维护。如果用 Excel 存放数据,在使用数据时写一个读取 Excel 数据的方法,然后采用ddt调用数据即可。
全部评论

相关推荐

5 13 评论
分享
牛客网
牛客企业服务