unittest框架
unittest框架是python的单元测试框架,属于黑盒测试,是web界面的功能测试的框架。
unittest 单元测试提供了创建测试用例,测试套件以及批量执行的方案, unittest 在安装pyhton 以后就直接自带了,直接import unittest 就可以使用。
作为单元测试的框架, unittest 也是可以对程序最小模块的一种敏捷化的测试。在自动化测试中,我们虽然不需要做白盒测试,但是必须需要知道所使用语言的单元测试框架。
利用单元测试框架,创建一个类,该类继承unittest的TestCase,这样可以把每个case看成是一个最小的单元, 由测试容器组织起来,到时候直接执行,同时引入测试报告。
unittest各组件的关系:
Test Fixture 测试固件
进行初始化和清理测试环境,其中setUp()和setDown()方法是最常用的方法
- setUp()方法:主要进行初始化的操作
- tearDown()方法:主要进行清理工作
在每一次执行测试方法的时候,测试固件都会执行一次
在运行一个测试类时,测试固件是默认执行的。
TestCase 测试用例类
TestCase是编写单元测试用例最常用的类
在做单元测试时编写的测试用例就是继承unittest中的TestCase类来实现具体的测试用例。
每一个继承TestCase类的子类里面实现的具体的方法(以test_开头的方法)都是一条用例 。
在一个类中,除了测试固件之外,所有以test_开头的方法都是默认运行的,其他的方法必须调用才能够使用。
import time
import unittest
from selenium import webdriver
import testlearn
from selenium.common.exceptions import NoAlertPresentException
# test1类继承TestCase
class testCase1(unittest.TestCase):
# 测试固件:setUp() 和tearDown()
# self代表这个类的实例
# 在执行测试方法的时候,测试固件每一次都会执行
def setUp(self):
# 初始化操作
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
# 清理工作
self.driver.quit()
# 除了测试固件之外,所以以test_开头的方法是默认运行的
def test_baidu1(self):
driver = self.driver
driver.find_element_by_id("kw").send_keys("123")
driver.find_element_by_id("su").click()
time.sleep(3)
def test_baidu2(self):
driver = self.driver
driver.find_element_by_link_text("新闻").click()
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
# 入口
if __name__ == '__main__':
testlearn.main(verbosity=0)
TestSuite 测试套件
对于一个功能的验证往往是需要很多很多测试用例,通常需要编写多个测试用例才能对某一软件的功能进行比较完整的测试,这些相关的测试用例称为一个测试用例集,在unittest中是用TestSuite类来表示。
TestSuite 用来组装测试用例,规定用例的执行顺序。
当有多个或者几百测试用例的时候,就需要一个测试容器(测试套件),把测试用例放在该容器中进行执行。
unitest模块提供的TestSuite 类来生成测试套件,使用该类的构造函数可以生成一个测试套件的实例,该类中提供了addTest来把每个测试用例加入到测试套件中。
addTest():
将测试方法一个一个加入到测试套件中,如果一个py文件中有10个case,就需要增加10次。
test001.py
import time
import unittest
from selenium import webdriver
import testlearn
from selenium.common.exceptions import NoAlertPresentException
# test1类继承TestCase
class testCase1(unittest.TestCase):
# 测试固件:setUp() 和tearDown()
# self代表这个类的实例
# 在执行测试方法的时候,测试固件每一次都会执行
def setUp(self):
# 初始化操作
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
# 清理工作
self.driver.quit()
# 除了测试固件之外,所以以test_开头的方法是默认运行的
def test_baidu1(self):
driver = self.driver
driver.find_element_by_id("kw").send_keys("123")
driver.find_element_by_id("su").click()
time.sleep(3)
def test_baidu2(self):
driver = self.driver
driver.find_element_by_link_text("新闻").click()
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
# 入口
if __name__ == '__main__':
testlearn.main(verbosity=0)
test002.py
import time
import unittest
from selenium import webdriver
import testlearn
from selenium.common.exceptions import NoAlertPresentException
class testCase2(unittest.TestCase):
def setUp(self):
# 初始化操作
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
# 清理工作
self.driver.quit()
# 除了测试固件之外,所以以test_开头的方法是默认运行的
def test_baidu1(self):
driver = self.driver
driver.find_element_by_id("kw").send_keys("杨洋")
driver.find_element_by_id("su").click()
time.sleep(3)
def test_baidu2(self):
driver = self.driver
driver.find_element_by_link_text("hao123").click()
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
# 入口
if __name__ == '__main__':
testlearn.main(verbosity=0)
testSuite.py
from testlearn import test001
import test002
import unittest
def createSuite():
# 组织测试套件
suite = unittest.TestSuite()
# addTest() 将每个测试方法依次添加到测试套件中
suite.addTest(test001.testCase1("test_baidu1"))
suite.addTest(test001.testCase1("test_baidu2"))
suite.addTest(test002.testCase2("test_baidu1"))
suite.addTest(test002.testCase2("test_baidu2"))
return suite
# 入口
if __name__ == '__main__':
suite = createSuite()
# 执行
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)# 执行这个测试套件
makSuite()和TestLoader()
把一个测试类添加到测试套件中。(如果一个类中有10个test_开头的case,那么使用addTest需要添加10次,使用makeSuite 将这个类中所有的以test_开头的方法添加到测试套件中)
makeSuite():
在unittest 框架中提供了makeSuite() 的方法,
makeSuite可以实现把测试用例类内所有的测试case组成的测试套件TestSuite ,
unittest 调用makeSuite的时候,只需要把测试类名称传入即可。
TestLoader():
用于创建类和模块的测试套件
一般的情况下,使用TestLoader().loadTestsFromTestCase(TestClass)来加载测试类。
from testlearn import test001
import test002
import unittest
def createSuite():
# 组织测试套件
suite = unittest.TestSuite()
# 2.makeSuite
# suite.addTest(unittest.makeSuite(test001.testCase1))
# suite.addTest(unittest.makeSuite(test002.testCase2))
# return suite
# TestLoader
suite1 = unittest.TestLoader().loadTestsFromTestCase(test001.testCase1)
suite2 = unittest.TestLoader().loadTestsFromTestCase(test002.testCase2)
suite.addTests([suite1,suite2]) # 将两个小的测试套件添加到测试套件suite中
return suite
# 入口
if __name__ == '__main__':
suite = createSuite()
# 执行
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)# 执行这个测试套件
经过makSuite()和TestLoader()的引入,一个测试类只需要导入一次即可,但是由于项目测试的文件会比较多,所以这种一个类一个类的添加还是很麻烦。
discover()
discover 是通过递归的方式到其子目录中从指定的目录开始, 找到所有测试模块并返回一个包含它们对象的TestSuite ,然后进行加载与模式匹配唯一的测试文件。
discover 参数分别为discover(dir,pattern,top_level_dir=None)
-
start_dir:要测试的模块名或测试用例目录;
-
pattern=‘test*.py’:表示用例文件名的匹配原则,下面的例子中匹配文件名为以“test”开头的“.py”文件;
-
top_level_dir=None:测试模块的顶层目录,如果没有顶层目录,默认为None;
import unittest
def createSuite():
# 组织测试套件
suite = unittest.TestSuite()
# discover()
discover = unittest.defaultTestLoader.discover('../testlearn',pattern='test00*.py',top_level_dir=None)
print(discover)
return discover
if __name__ == '__main__':
suite = createSuite()
# 执行
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)# 执行这个测试套件
TestRunner 执行测试用例
通过TextTestRunner类实例的run方法去执行的用例或用例集。
if __name__ == '__main__':
suite = createSuite()
# 执行
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)# 执行这个测试套件
- verbosity :表示测试结果的信息详细程度,一共三个值,默认是1
- 0 (静默模式):你只能获得总的测试用例数和总的结果 比如 总共100个 失败20 成功80
- 1 (默认模式):非常类似静默模式 只是在每个成功的用例前面有个 . 每个失败的用例前面有个 F
- 2 (详细模式):测试结果会显示每个测试用例的所有相关的信息
测试用例的执行顺序
unittest框架默认加载测试用例的顺序是根据ASCII码的顺序,数字与字母的顺序为0~9,A~Z,a~z 。
所以, TestAdd 类会优先于TestBdd 类被发现, test_aaa() 方法会优先于test_ccc() 被执行。
对于测试目录与测试文件来说, unittest 框架同样是按照这个规则来加载测试用例。
addTest()方法按照增加的顺序来执行。
忽略用例执行
@unittest.skip("skipping")
在测试方法前面加上以上代码,在执行用例时就会忽略该用例。
unittest断言
自动化的测试中, 对于每个单独的case来说,一个case的执行结果中, 必然会有期望结果与实际结果, 来判断该case是通过还是失败;
在unittest 的库中提供了大量的实用方法来检查预期值与实际值, 来验证case的结果;
一般来说, 检查条件大体分为等价性, 逻辑比较以及其他, 如果给定的断言通过, 测试会继续执行到下一行的代码, 如果断言失败, 对应的case测试会立即停止或者生成错误信息( 一般打印错误信息即可) ,但是不要影响其他的case执行。
unittest 的单元测试库中提供的常用的断言方法:
def test_baidu1(self):
driver = self.driver
driver.find_element_by_id("kw").send_keys("杨洋")
driver.find_element_by_id("su").click()
# 加载出来之后driver.title才会改变
time.sleep(3)
print(driver.title)
self.assertEqual(driver.title,"杨洋_百度搜索",msg="not equal")
self.assertNotEqual(driver.title,"百度搜索",msg="equal")
HTML报告生成
把所有的测试结果输出到一个HTML的测试报告中,在其中会统计运行了测试用例,通过了多少,失败了多少以及失败的原因等等。
脚本执行完毕之后,需要看到HTML报告,通过HTMLTestRunner.py来生成测试报告。
HTMLTestRunner.py 文件,
下载地址: http://tungwaiyip.info/software/HTMLTestRunner.html
下载后将其放在testcase目录中去或者放入...\Python27\Lib 目录下(windows)
import os
import sys
import unittest
import HTMLTestRunner
import time
def createSuite():
# 使用discover构建测试套件,testlearn中所有以test00开头的文件中的test_开头的方法加入测试套件中
discover = unittest.defaultTestLoader.discover('./testlearn', pattern='test00*.py', top_level_dir=None)
return discover
if __name__ == '__main__':
# 获取当前文件所在的文件路径
curpath = sys.path[0]
print(curpath)
if not os.path.exists("/resultReport"):
os.mkdirs(curpath+"/resultReport")
# 根据时间命名,防止文件名称重复
# 把Linux时间戳转换成本地的时间
now = time.strftime("%Y-%m-%d-%H %M %S",time.localtime(time.time()))
filename = curpath+"/resultReport/"+now+"-"+"resultReport.html"
# 输出测试结果
# 加u,防止乱码
with open(filename,'wb') as fp:
runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title=u"测试报告",
description=u"用例执行情况",verbosity=2)
suite = createSuite() # 测试套件
runner.run(suite) # 运行测试套件
输出的测试报告:
异常捕获和错误截图
用例不可能每一次运行都执行成功,肯定运行时候有不成功的时候,如果可以捕获到错误,并且把错误截图保存下来,会给我们错误定位带来方便。
driver.get_screenshot_as_file
def test_baidu2(self):
driver = self.driver
# 判断是不是打开百度页面
try:
# 判断driver.title是不是相同
self.assertEqual(driver.title,"百度,你就知道",msg= "没有打开百度搜索页面")
except:
self.save_error_image(driver,"baidu.png") #保存错误截图
time.sleep(3)
def save_error_image(self,driver,name):
if not os.path.exists("./errorImage"):
os.makedirs("./errorImage")
now = time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time()))
self.driver.get_screenshot_as_file('./errorImage/'+now+'-'+name)
错误截图:
数据驱动
需要多次执行一个案例,比如baidu搜索,分别输入中文、英文、数字等进行搜索,这时候需要编写3个案例吗?
import csv
from selenium import webdriver
import unittest
import time
import os,sys
from ddt import ddt,file_data,unpack,data
# 读取文件中的内容
def getCsv(file_name):
# 存放读取出来的数据
rows=[]
path = sys.path[0]
print(path)
with open(path+'\\'+file_name,'rt',encoding="UTF-8") as f:
readers = csv.reader(f,delimiter=',',quotechar='|')
next(readers,None)
for row in readers:
#[周迅,周迅_百度搜索]
temprows=[]
for i in row:
temprows.append(i)
rows.append(temprows)
#([周迅,周迅_百度搜索],[张杰,张杰_百度搜索])
return rows
@ddt
class testCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com"
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
# @data("迪丽热巴","玉兔","棉花","Lisa")
# @file_data('test_baidu_data.json') # 直接把文件名引进来就可以了,美运行一次传一个数据
# @data(["Jolin","Jolin_百度搜索"],["玉兔","玉兔_百度搜索"]) # 每一次传进去的是一个元组
# @unpack
def test_baidu1(self,value1,value2):
driver = self.driver
driver.get(self.url)
driver.find_element_by_id("kw").send_keys(value1)
driver.find_element_by_id("su").click()
time.sleep(4)
print(driver.title)
self.assertEqual(driver.title,value2,msg="fail!!!!")
time.sleep(3)
@data(*getCsv("test_baidu_data.txt"))
@unpack
def test_baidu2(self,value,expected_value):
driver = self.driver
driver.get(self.url)
driver.find_element_by_id("kw").send_keys(value)
driver.find_element_by_id("su").click()
time.sleep(3)
self.assertEqual(driver.title,expected_value,msg="和期望值不一样!!!")
time.sleep(3)
if __name__ == '__main__':
unittest.main(verbosity=2)
@data("迪丽热巴","玉兔","棉花","Lisa") # 依次传入数据,执行测试脚本
@file_data('test_baidu_data.json') # 直接把文件名引进来就可以了,每运行一次传一个数据
@data(["Jolin","Jolin_百度搜索"],["玉兔","玉兔_百度搜索"]) # 每一次传进去的是一个元组
@unpack
@data(*getCsv("test_baidu_data.txt")) # 读取txt文件中的内容
@unpack