【软件测试】自动化测试战零基础教程——Python自动化从入门到实战(七)

本文介绍了如何结合seleniumIDE和unittest进行软件测试,包括seleniumIDE的安装、界面、录制与编辑脚本,以及unittest框架的引入、用例组织与执行。讲解了unittest的基本结构,如setUp、tearDown方法,以及如何批量执行测试用例。通过实例展示了unittest的用法,帮助读者快速掌握自动化测试基础。
摘要由CSDN通过智能技术生成

整理不易,希望对各位学习软件测试能带来帮助这里是引用

第六章 引入 unittest 单元测试框架

第一节、selenium IDE 介绍

selenium IDE 是 selenium 家族的中的一员,它是嵌入到 firefox 浏览器的一个插件,这里之所以要介绍 selenium IDE 是因为我们可以将 selenium IDE 录制的脚本转换成不同语言脚本,有助于帮助我们尽快熟悉脚本语言以及测试框架。

6.1.1 selenium IDE 安装

通过 firefox 浏览器访问 selenium 下载页面:http://docs.seleniumhq.org/download/
在 selenium IED 下载介绍部分,点击版本号链接,如图 :
图 6.1

firefox 浏览器将自动识别需要下载的 selenium IED 插件,如图 ,点击 Install Now 按钮,安
装 selenium IED 插件。

在这里插入图片描述

安装完成后重启 firefox 浏览器,通过菜单栏“Tools(工具)”—> selneium IDE 可以打开,或通过 Ctrl+Alt+S 快捷键打开。
在这里插入图片描述

6.1.2 selenium IDE 界面介绍

在这里插入图片描述

为了方便简洁,我们就按照上图的数字标记介绍 selenium IDE 界面各个部分人作用:
1—文件(File):创建、打开和保存测试案例和测试案例集。
编辑(Edit):复制、粘贴、删除、撤销和选择测试案例中的所有命令。
Options (设置): 用于设置 seleniunm IDE。
**2—**用来填写被测网站的地址。
**3—**速度控制:控制案例的运行速度。
**4—**运行所有:运行一个测试案例集中的所有案例。
**5—**运行:运行当前选定的测试案例。
**6—**暂停/恢复:暂停和恢复测试案例执行。
7—|单步:可以运行一个案例中的一行命令。
**8—**录制:点击之后,开始记录你对浏览器的操作。
**9—**案例集列表。
**10—**测试脚本;table 标签:用表格形式展现命令及参数。source 标签:用原始方式展现,默认是 HTML 语言格式,也可以用其他语言展示。
**11—**查看脚本运行通过/失败的个数。
**12—**当选中前命令对应参数。
**13—**日志/参考/UI 元素/Rollup
日志:当你运行测试时,错误和信息将会自定显示。
参考:当在表格中输入和编辑 selenese 命令时,面板中会显示对应的参考文档。
UI 元素/Rollup:参考帮助菜单中的,UI-Element Documentation。

6.1.3 selenium IDE 录制脚本

打 开 selenium IDE 录 制 按 钮 默 认 为 启 动 状 态 , 在 地 址 栏 中 输 入 要 录 制 的 URL ( 如 ,
http://www.baidu.com),脚本录制完成,关闭录制按钮,如图

在这里插入图片描述

6.1.4 selenium IDE 编辑脚本

selenium IDE 为我们录制的脚本不是100%符合我们的需求的,所以,编辑录制的脚本是必不可少的工作。

  1. 编辑一行命令或注释。
    在 Table 标签下选中某一行命令,命令由 command、Target、value 三部分组成。可以对这三部分内容那进行编辑。
    在这里插入图片描述

  2. 插入命令
    在某一条命令上右击,选择“insert new command”命令,就可以插入一个空白,然后对空白行进程编辑。

在这里插入图片描述

  1. 插入注解
    以上面同样的方式右击选择“insert new comment”命令插入注解空白行,本行内容不被执行,可以帮助我们更好的理解脚本,插入的内容以紫色字体显示。
    在这里插入图片描述

  2. 移动命令或注解
    有时我们需要移动某行命令的顺序,我们只需要左击鼠标拖动到相应的位置即可。

在这里插入图片描述

  1. 定位辅助
    当 selenium IDE 录制脚本时,它会存储额外的信息,支持用户挑选其他格式的定位器来代替默认格
    式的定位器,这种特殊性对于学习定位器很有用。
    在这里插入图片描述

我们可以选择其他的命令来代替“name=btnG” 命令,当然,脚本依然是可以运行的。
关于 selenium IDE 的更多使用技巧不在不是本书的讨论重点,请读者参考其资料进行学习。

第二节、引入 unittest 框架

通过 seleinium IDE 完成脚本的录制之后,可以将其导出为加了 python unittest 单元测试框架的相应脚本。如图 6.10 。
在这里插入图片描述

将脚本导出,保存为 baidu.py ,通过 python IDLE 编辑器打开。如下:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
import unittest, time, re
class Baidu(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "http://www.baidu.com/" self.verificationErrors = []
self.accept_next_alert = True
def test_baidu(self):
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").send_keys("selenium webdriver")
driver.find_element_by_id("su").click()
driver.close()
def is_element_present(self, how, what):
try: self.driver.find_element(by=how, value=what)
except NoSuchElementException, e: return False
return True
def is_alert_present(self):
try: self.driver.switch_to_alert()
except NoAlertPresentException, e: return False
return True
def close_alert_and_get_its_text(self):
try:
alert = self.driver.switch_to_alert()
alert_text = alert.text
if self.accept_next_alert:
alert.accept()
else:
alert.dismiss()
return alert_text
finally: self.accept_next_alert = True
def tearDown(self):
self.driver.quit()
self.assertEqual([], self.verificationErrors)
if __name__ == "__main__":
unittest.main()

加入 unittest 框架后,看上去比我们之前见的脚本复杂了很多,除了中间操作浏览器的几行是我们所
熟悉的脚本,其它的方法都是做什么用的?我们来简单分析一下!

import unittest

首先要引入 unittest 框架包。

class Baidu(unittest.TestCase):

Baidu 类继承 unittest.TestCase 类,从 TestCase 类继承是告诉 unittest 模块的方式,这是一个测试案例。

def setUp(self):
self.driver = webdriver.Firefox()
self.base_url = "http://www.baidu.com/"

setUp 用于设置初始化的部分,在测试用例执行前,这个方法中的函数将先被调用。这里将浏览器的调用和 URL 的访问放到初始化部分。

self.verificationErrors = []

脚本运行时,错误的信息将被打印到这个列表中。

self.accept_next_alert = True

是否继续接受下一个警告。

def test_baidu(self):
driver = self.driver
driver.get(self.base_url +“/”)
driver.find_element_by_id(“kw”).send_keys(“selenium webdriver”)
driver.find_element_by_id(“su”).click()

test_baidu 中放置的就是我们的测试脚本了,这部分我们并不陌生;因为我们执行的脚本就在这里。

def is_element_present(self, how, what):
try:self.driver.find_element(by=how, value=what)
except NoSuchElementException, e: return False
return True

is_element_present 函数用来查找页面元素是否存在,try…except…为 python 语言的异常捕捉。
is_element_present 函数在这里用处不大,通常删除,因为判断页面元素是否存在一般都加在 testcase 中。

def is_alert_present(self):
try: self.driver.switch_to_alert()
except NoAlertPresentException, e: return False
return True

对弹窗异常的处理

def close_alert_and_get_its_text(self):
try:
alert =self.driver.switch_to_alert()
alert_text = alert.text
if self.accept_next_alert:
alert.accept()
else: alert.dismiss()
returnalert_text
finally: self.accept_next_alert = True

关闭警告以及对得到文本框的处理,if 判断语句前面已经多次使用,并不陌生;try…finally…为 python
的异常处理。

def tearDown(self):
self.driver.quit()
self.assertEqual([],self.verificationErrors)

tearDown 方法在每个测试方法执行后调用,这个地方做所有测试用例执行完成的清理工作,如退出
浏览器等。
self.assertEqual([], self.verificationErrors)
这个是难点,对前面 verificationErrors 方法获得的列表进行比较;如查 verificationErrors 的列表不为空,输出列表中的报错信息。

if name == “main”:
unittest.main()

unitest.main()函数用来测试 类中以 test 开头的测试用例
这样一一分析下来,我们对 unittest 框架有了初步的了解。运行脚本,因为引入了 unittest 框架,
所以控制台输出了用例的执行个数、时间以及是否 OK 等信息。

>>> ========================= RESTART ================================
>>>
.---------------------------------------------------------------------- 

Ran 1 test in 10.656s


OK
>>>

本节只是初步对 unittest 的框架进行了分析,还有许细节将放在后面章节进行讨论,例如 python 的异常处理机制等。

第三节、unittest 单元测试框架解析

通过上一节的学习我们对 unittest 框架了初步的了解。这一节将详细介绍 unittest 框架是如何帮助我们完成单元测试的。因为 unittest 是我们后续开展自动化测试的基础,所以,请读者认真学习 unittest 框架的
使用。
单元测试负责对最小的软件设计单元(模块)进行验证,它使用软件设计文档中对模块的描述作为指南,对重要的程序分支进行测试以发现模块中的错误。unittest 框架(又名 PyUnit 框架)为 python 语言
的单元测试框架,从 Python 2.1 及其以后的版本都将 PyUnit 作为一个标准模块放入 python 开发包中。

下面通过具体例子介绍 unittest 的使用:
widget.py(被测试类)

#coding= utf-8
# 将要被测试的类
class Widget:
def __init__(self, size = (40, 40)):
self._size = size
def getSize(self):
return self._size
def resize(self, width, height):
if width < 0 or height < 0:
raise ValueError, "illegal size"
self._size = (width, height)
def dispose(self):
pass

python 基础知识补充:

init()
init()方法在类的一个对象被建立时,马上运行。这个方法可以用来对你的对象做一些你希望的初
始化。
getSize()
在面向对象的编程语言中都会有类的概念,类具有封装性;在 C++ 、java 等语言中通过 private(私
有)、protected(保护)、public(公有)等修饰符来限定访问权限。在 Python 中没有显式的 private 和 public限定符,如果要将一个方法声明为 private 的,只要在方法名前面加上“ __ ”即可。

所以,我们前面定义的__init__() 方法是一个私有的方法,不能直接被外部使用。那么如何才能使用
类中私有的成员函数着,就通过 getXX 和 setXX 方法来访问。一个赋值函数(getXX),一个取值函数
(setXX )。

从上面的例子中看到,init()方法中默认参数是 size=(40, 40) 在函数体中定义 self._size = size。通
过变量传递,self._size=(40, 40) ,但 self._size 是私有的,不可被类以外的方法和函数调用。(self 表示类本身,后面章节中会进一步解释。)

所以,在 getSize()方法中定义返回 self.size,那么就可以调用 getSize()方法来使用__init_()方法中
self._size。
auto.py(测试类)

#coding= utf-8
from widget import Widget
import unittest
# 执行测试的类
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget()
def testSize(self):
self.assertEqual(self.widget.getSize(), (40, 40))
def tearDown(self):
self.widget = None
# 构造测试集
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase("testSize"))
return suite

# 测试
if __name__ == "__main__":
unittest.main(defaultTest = 'suite')
  • 用 import 语句引入 unittest 模块
  • 让所有执行测试的类都继承于 TestCase 类,可以将 TestCase 看成是对特定类进行测试的方法的集合
  • setUp()方法中进行测试前的初始化工作,tearDown()方法中执行测试后的清除工作。setUp()和 tearDown()都是 TestCase 类中定义的方法
  • 在 testSize()中调用 assertEqual()方法,对 Widget 类中 getSize()方法的返回值和预期值进行比较,确保两者是相等的,assertEqual()也是 TestCase 类中定义的方法。
  • 提供名为 suite()的全局方法,PyUnit 在执行测试的过程调用 suit()方法来确定有多少个测试用例需要被执行,可以将 TestSuite 看成是包含所有测试用例的一个容器。

框架分析

软件测试中最基本的组成是单元测试用例(test case),我们在实际测试过程中,不可能真对一个功能
(类)只写一个用例。TestCase 在 PyUnit 测试框架中被视为测试单元的运行实体,Python 程序员可以通过它派生自定义的测试过程与方法(测试单元),利用 Command 和 Composite 设计模式,多个 TestCase 还可以组合成测试用例集合。

编写测试用例

采用 PyUnit 提供的动态方法,只编写一个测试类来完成对整个软件模块的测试,这样对象的初始化工
作可以在 setUp()方法中完成,而资源的释放则可以在 tearDown()方法中完成。
对的 widget.py 被测试类的多方法进行测试。

# 执行测试的类
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget()
# 测试 getSize()方法的测试用例
def testSize(self):
self.assertEqual(self.widget.getSize(), (40, 40))
# 测试 resize()方法的测试用例
def testResize(self):
self.widget.resize(100, 100)
self.assertEqual(self.widget.getSize(), (100, 100))
def tearDown(self):
self.widget.dispose()
self.widget = None

我们可以在一个测试类中,写多个测试用例对被测试类的方法进行测试。

组织用例集

完整的单元测试很少只执行一个测试用例,开发人员通常都需要编写多个测试用例才能对某一软件功
能进行比较完整的测试,这些相关的测试用例称为一个测试用例集,在 PyUnit 中是用 TestSuite 类来表示的。
可以在单元测试代码中定义一个名为 suite()的全局函数,并将其作为整个单元测试的入口,PyUnit 通过调用它来完成整个测试过程。

def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase("testSize"))
suite.addTest(WidgetTestCase("testResize"))
return suite

如果用于测试的类中所有的测试方法都以 test 开头,Python 程序员甚至可以用 PyUnit 模块提供的
makeSuite()方法来构造一个。

def suite():
return unittest.makeSuite(WidgetTestCase, "test")

TestSuite 类可以看成是 TestCase 类的一个容器,用来对多个测试用例进行组织,这样多个测试用例可以自动在一次测试中全部完成。

运行测试集

PyUnit 使用 TestRunner 类作为测试用例的基本执行环境,来驱动整个单元测试过程。Python 开发人员
在进行单元测试时一般不直接使用 TestRunner 类,而是使用其子类 TextTestRunner 来完成测试,并将测试结果以文本方式显示出来:

runner = unittest.TextTestRunner()
runner.run(suite)

对 widget.py 被测试类,下面通过 PyUnit 编写完整的单元测试用例:
text_runner.py

#coding=utf-8
from widget import Widget
import unittest
# 执行测试的类
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget()
def tearDown(self):
self.widget.dispose()
self.widget = None
def testSize(self):
self.assertEqual(self.widget.getSize(), (40, 40))
def testResize(self):
self.widget.resize(100, 100)
self.assertEqual(self.widget.getSize(), (100, 100))
# 测试
if __name__ == "__main__":
# 构造测试集
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase("testSize"))
suite.addTest(WidgetTestCase("testResize"))
# 执行测试
runner = unittest.TextTestRunner()
runner.run(suite)

PyUnit 模块中定义了一个名为 main 的全局方法,使用它可以很方便地将一个单元测试模块变成可以直接运行的测试脚本,main()方法使用 TestLoader 类来搜索所有包含在该模块中的测试方法,并自动执行它们。如果 Python 程序员能够按照约定(以 test 开头)来命名所有的测试方法,那就只需要在测试模块的最后加入如下几行代码即可:

#coding=utf-8
from widget import Widget
import unittest
# 执行测试的类
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget()
def tearDown(self):
self.widget.dispose()
self.widget = None
def testSize(self):
self.assertEqual(self.widget.getSize(), (40, 40))
def testResize(self):
self.widget.resize(100, 100)
self.assertEqual(self.widget.getSize(), (100, 100))
# 测试
if __name__ == "__main__":
unittest.main()

python 基础知识补充:

if name == “main”:语句说明
后面我们会经常用到这个语句,在解释之前先补充点 python 知识:
1、python 文件的后缀为.py;
2、.py 文件既可以用来直接执行,就像一个小程序一样,也可以用来作为模块被导入
3、在 python 中导入模块一般使用的是 import
顾名思义,if 就是如果的意思,在句子开始处加上 if,就说明,这个句子是一个条件语句。接着是
__ name__,__ name__作为模块的内置属性,简单点说呢,就是.py 文件的调用方式。最后是__ main__,刚才我也提过,.py 文件有两种使用方式:作为模块被调用和直接使用。如果它等于"__ main__"就表示是直接执行。

我们在实际的自动化测试用例开发过程中,首先要保证开发的单个用例文件(.py)是运行通过的,如
何跑单个文件上的用例,那么就可以在 if __ name__ == “__ main__”:后面编写执行用的语句,如上面介绍的 TextTestRunner()方法来构造测试集,或直接使用 unittest.main()来运行所有用例。

那么一旦这个用例文件(.py)稳定之后,就需要将这个用例文件添加到用例集中,这个用例文件就被
做为一个模块被调用;这个时候 if __ name__ == “__ main__”:后面的内容将不会被执行。

第四节、批量执行测试用例

通过对前面对 unittest 框架的学习我们了解到,可以在一个.py 文件里编写多个测试用例,然后执行文件里的所有用例,这显然是一个不错的做法,我们可以将一些相关的用例放到一个文件里,unittest 支持这么做,但假如我们成百上千的用例呢,放一.py 文件显然有些不太合理。
比较合理的做法是把相关的几条用例放到一个.py 文件里,把所有.py 文件放到一个文件夹下,然后通过一个程序执行文件夹下面的所有用例。
在这里插入图片描述

如图显然是一个比较理想的效果,下面来组织这样一个结构。
编写 baidu.py 文件放入 test_case 文件夹下:

#coding=utf-8
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
import unittest, time, re
class Baidu(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "http://www.baidu.com/"
self.verificationErrors = []
self.accept_next_alert = True
#百度搜索用例
def test_baidu_search(self):
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").send_keys("selenium webdriver")
driver.find_element_by_id("su").click()
time.sleep(2)
driver.close()
#百度设置用例
def test_baidu_set(self):
driver = self.driver
#进入搜索设置页
driver.get(self.base_url + "/gaoji/preferences.html")
#设置每页搜索结果为 100 条
m=driver.find_element_by_name("NR")
m.find_element_by_xpath("//option[@value='100']").click()
time.sleep(2)
#保存设置的信息
driver.find_element_by_xpath("//input[@value='保存设置']").click()
time.sleep(2)
driver.switch_to_alert().accept()
def tearDown(self):
self.driver.quit()
self.assertEqual([], self.verificationErrors)
if __name__ == "__main__":
unittest.main()

下面编写 test_case.py 点文件来读取 test_case 文件夹下的文件:

#-*-coding=utf-8 -*-
import os
#列出某个文件夹下的所有 case,这里用的是 python,
#所在 py 文件运行一次后会生成一个 pyc 的副本
caselist=os.listdir('D:\\selenium_use_case\\test_case')
for a in caselist:
s=a.split('.')[1] #选取后缀名为 py 的文件
if s=='py':
#此处执行 dos 命令并将结果保存到 log.txt
os.system('D:\\selenium_use_case\\test_case\\%s 1>>log.txt 2>&1'%a)

python 知识补充:
python 的 os 模块可以用来操作本地文件,通过 os.listdir()函数获得指定目录中的内容;下面通过小例子单独理解这段程序的匹配用法。打开 python IDLE 的交互模式输入以下代码。

>>> x = 'testing.py'
>>> s = x.split('.')[1]
>>> if s=='py':
print s
py

split()用于字符串分割,本例中以文件名的点(.)作为分割。被分割之的字符串‘testing.py’会变成[‘testing’,‘py’] ,[n]表示数组,因为数组是从0开始计算的,所以[0]取的是‘testing’,那么[1]取的就是‘py’。

把取到的结果赋值给 s ,if 判断 s 等于(==)‘py’则执行后面的语句。

os.system(‘D:\selenium_use_case\test_case\%s 1>>log.txt 2>&1’ %a) 语句根据上面的判断
条件选取D:\selenium_use_case\test_case\目录下的文件执行,并将执行结果保存到log.txt文件中。
查看 log.txt 文件:

..
---------------------------------------------------------------------- 
Ran 2 tests in 32.469s

OK

到目前为止,一个极其简陋的自动化测试环境完成,我们要做的就是编写测试用例放入 test_case 文
件夹下,通过 test_case.py 程序执行所以测试用例。然后通过 log.txt 查看脚本的执行情况。

如果脚本运行失败,log.txt 将显示出错信息。如下:

======================================================================
ERROR: test_baidu_search (__main__.Baidu)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\selenium_python\test_case\baidu.py", line 22, in test_baidu_search
driver.find_element_by_id("kwss").send_keys("selenium webdriver")
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 198, in
find_element_by_id
return self.find_element(by=By.ID, value=id_)
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 680, in
find_element
{'using': by, 'value': value})['value']
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 165, in
execute
self.error_handler.check_response(response)
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 158,
in check_response

raise exception_class(message, screen, stacktrace)
NoSuchElementException: Message: u'Unable to locate element:
{"method":"id","selector":"kwss"}' ; Stacktrace:
at FirefoxDriver.findElementInternal_
(file:///c:/docume~1/admini~1/locals~1/temp/tmptamufm/extensions/fxdriver@googlecode.com/co
mponents/driver_component.js:8444)
at fxdriver.Timer.setTimeout/<.notify
(file:///c:/docume~1/admini~1/locals~1/temp/tmptamufm/extensions/fxdriver@googlecode.com/co
mponents/driver_component.js:386)
----------------------------------------------------------------------
Ran 2 tests in 44.360s
FAILED (errors=1)

通过上面的错误信息,我们可以比较清晰的判断脚本执行错误的发生为止。如上面的错误信息,我们
修改百度输入框的定位 find_element_by_id(“kwss”),导致脚本无法定位到输入框。

由于引入 unittest 框架的作用,一条用例失败后不会影响下一条用例的执行。因此,从运行结果我
们可以看到,跑了两条用例,失败为(errors=1)。

总结:

通过本章的学习,我们掌握了 selenium IDE 安装与使用,以及如何将 selenium IDE 录制的脚本导出为 python unittest 单元测试框架相应的脚本;初步学习了解了 unittest 单元测试框架的使用。如何python 语言批量的执行文件夹的用例文件。

未完待续

软件测试学习打卡交流地,每天还有教学直播哦【暗号:CSDN】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值