Python 自动化测试实战
Selenium 基础知识
selenium 介绍及在工作中的应用:
安装Selenium:
pip3 install --upgrade pip pip3 install selenium==3.14.0
配置Selenium环境:
既然名为网页浏览器自动化自然要安装浏览器,一般来说,Chrome、Firefox等浏览器都可以,这里我们使用当前系统自带的Firefox作为实验浏览器。
通常,我们需要去下载浏览器对应的驱动,比如 Firefox 浏览器要下载 geckodriver 驱动,然后放到 /usr/local/bin文件夹中。当前环境中已经内置了驱动,了解步骤即可。
打开终端,将目录切换至桌面:
$ cd /home/shiyanlou/Desktop
#! /usr/bin/python3 from selenium import webdriver driver = webdriver.Firefox() driver.get("https://www.lanqiao.cn")
输入python3 demo.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等。
#! /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 命令,第二个是目标元素。
#! /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 知识与实战
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
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.再对无效数据进行测试- 无效数据需要单独测试(为了避免屏蔽现象)
- 3.最后对多个无效数据组合测试(适当强化)
- 因果图
- 适用场合:界面中考虑控件的组合和限制关系的情况(组合数量较少)。
- 因果图中常用的 9 个图形符号:恒等,与,或,非;互斥(E),唯一(O),包含(I),要求(R),屏蔽(M);
- 使用步骤:
+ 找出所有的输入条件(因),和所有的输出结果(果); + 找出输入条件的所有组合和限制; + 每组输入条件组合对应的输出结果,画因果图,填判定表(画因果图可以省略); + 编写测试用例,每一列对应一条测试用例。
- 场景法
- 适用场合:当需要测试软件的业务流程(逻辑)时,适合用场景法,场景法是基于业务的方法,有测试人员模拟用户在使用软件的各种不同的情况;
- 场景法划分:基本流和备选流 > 基本流:也叫有效流或正确流,模拟用户正确的操作的过程; 备选流:也叫无效流或错误流,模拟用户错误的操作的情景;
- 场景法的使用步骤:
+ 分析需求,整理业务流程(逻辑),列出场景; + 根据列出场景填写场景表; + 为每个场景编写适当的测试用例(不一定是1:1的)。
- 正交排列
- 适用场合:对于参数配置类软件,以及兼容性测试时需要考虑各个控件之间的组合情况(组合较多),使用正交排列法选择较少的组合达到最佳的测试效果。
- 使用步骤:
- 分析需求,列出参与组合的控件以及每个控件的取值;
- 选择合适的正交表(确定 m = 控件取值个数, k = 控件数);
- 完成控件,控件取值对应因子和状态的映射;
- 编写测试用例。
自动化测试用例设计规范
自动化测试的用例与功能测试的用例不同,功能测试的测试用例讲究全面覆盖,也就是说如果穷举法可以覆盖所有测试点的话那么穷举就是功能测试最好的方法。但是自动化测试用例则不同,总结起来自动化测试用例有如下特点:
- 少而精
- 用例健壮
- 必有预期结果——断言
解释:
- 少而精。即写少量的用例覆盖大量的测试点。比如对提示语、点击按钮后的页面响应等可以用一个用例覆盖——这就要求我们在预期结果中对所有的判断进行说明。
- 用例健壮。我们知道,自动化测试主要是由代码实现的,我们在用代码实现测试点覆盖以后这个测试代码将会经常被使用。而我们的业务也在不断更新,试想如果每次更新业务都需要对自动化代码进行维护,那可能维护代码的时间反而与功能测试占用时间相差无几。所以自动化测试代码要讲究健壮性,即新功能上线不会对原有代码(自动化测试)造成大的影响。就像我们写代码时所讲的”面向对象“的思想——我们要把固定的模块单独作为测试用例,减少用例耦合,这样在某个模块有大的变动时就不会造成整体用例代码的修改了。
- 必有预期结果——断言。功能测试有预期结果进行验证,而在自动化测试中则称为断言。也就是代码中所有涉及到做了这个操作以后会……的场景都要添加断言,而在代码中断言不一定要用assert,如果必要的情况用if判断也同样可以。在代码中讲究的是灵活,而不是无脑的遵循代码的写法。
网易邮箱登录实战
2.3.1 测试用例
用例编号 | 测试点 | 执行步骤 | 预期结果 |
---|---|---|---|
1 | 登录成功 |
1. 输入用户账号 2. 输入用户密码 3.点击登录按钮 |
1. 登录框默认文字”邮箱帐号或手机号“ 2. 密码框默认文字”密码“ 3. 登录成功 4. 登录成功后显示用户名 |
2.3.2 分析
- 准备
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]。
- 点击登录按钮
driver.find_element_by_xpath('//*[@id="dologin"]').click()
# 切换出iframe driver.switch_to.default_content()
2.3.3 代码
下面我们就写代码测试网易邮箱的登录功能,大家如果没有网易邮箱的可以先注册一个,代码中将会使用到你的账号信息。
#! /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 单元测试介绍
首先导入unittest单元测试库
import unittest
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。
if __name__ == '__main__': unittest.main(verbosity=2)
然后执行该文件:
写脚本分析 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
#! /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)
执行文件:
setUpClass 与 tearDownClass
#! /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…执行以前和全部执行结束以后分别执行了一次。
跳过测试用例
#! /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被跳过执行了。
批量执行
$ cd /home/shiyanlou/Desktop $ mkdir MySelenium && cd MySelenium $ touch runTests.py && mkdir testCases
2.在testCases文件夹中新建空的__init__.py文件(有了这个文件,该文件夹就会被识别为包),并新建testMyTestUnittest.py文件和testIntegerArithmeticTestCase.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)
#! /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()
#! /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开头的方法。
#! /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 对自动化测试用例进行集成
使用 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
#! /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的文件,双击打开,可以看到:
将代码封装形成框架
- 封装思想
代码封装应用的是面向对象的思想,即将公共方法提取出来,用到的时候采用调用的方式。这样做的好处有四点:
i. 减少代码冗余
ii. 减少代码耦合
iii. 方便定位代码报错
iv. 方便代码维护
- 框架结构

- baseInfo文件夹存放用例用到的各种参数,如用户名、密码等
- modules文件夹存放各种固定方法,如发送邮件、获取测试用例、读取测试结果等
- report文件夹存放生成的测试报告
- testCases文件夹存放测试用例
#! /usr/bin/python3 from os import path def dir_path(): file_path = path.dirname(__file__) # 返回父级目录 return path.dirname(file_path)我们生成了测试报告以后需要以邮件的方式发送,所以我们需要写一个发送邮件的方法。这里需要注意,为了方便起见,邮件的标题最好就是测试的结果,所以我们把标题内容以参数的方式传递。
#! /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参数传递给发送邮件的方法.
#! /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]
#! /usr/bin/python3 ''' 发送邮件参数 ''' mail_server = '使用的邮箱服务器' # smtp.qq.com mail_sender = '发送邮件的邮箱' # xxxxxxxxxx@qq.com mail_password = '邮箱授权码' # 到QQ邮箱中获取邮箱授权码 mail_receiver = ['邮件接收人邮箱'] # 收件邮箱可以和发件邮箱相同
#! /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库。
#! /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()
可以看到用例数据被打印出来了。
查看 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执行文件,结果和上面相同: