1.什么是unittest框架
unittest 是python 的单元测试框架,它主要有以下作用:
- 提供用例组织与执行
当你的测试用例只有几条时,可以不必考虑用例的组织,但是,当测试用例达到成百上千条时,大量的测试用例堆砌在一起,就产生了扩展性与维护性等问题,此时需要考虑用例的规范与组织问题了。单元测试框架就是来解决这个问题的。 - 提供丰富的比较方法
在用例执行完之后都需要将实际结果与预期结果进行比较(断言),从而断定用例是否可以顺利通过。单元测试一般会提供丰富的断言方法。例如,判断相等/不相等、包含/不包含、True/False等断言方法。 - 提供丰富的日志
当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成后能提供丰富的执行结果。例如,总的执行时间,失败用例数,成功用例数等
2.unittest的简单使用
下面为unittest框架的脚本:
import unittest
from selenium import webdriver
import time
# 定义一个类继承unittest.TestCase
class Baidu1(unittest.TestCase):
# 测试固件
# 环境的初始化
def setUp(self):
print("--setUp--")
# self 定义的是全局变量,否则只能在本方法内部使用
self.driver = webdriver.Chrome()
# 访问的URL
self.url = "https://www.baidu.com"
self.driver.maximize_window()
time.sleep(3)
# 测试环境的还原
def tearDown(self):
print("--tearDown--")
self.driver.quit()
# 测试用例 必须以test_开头 否则不识别,测试用例的执行顺序按照测试方法的字典序升序执行
def test_t1(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_link_text("hao123").click()
driver.implicitly_wait(3)
print(driver.title)
time.sleep(3)
# 注解:忽略测试用例的执行
@unittest.skip("skipping")
def test_t2(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_id("kw").send_keys("胡歌")
driver.find_element_by_id("su").submit()
time.sleep(3)
print(driver.title)
time.sleep(3)
def test_t3(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_id("kw").send_keys("刘浩存")
driver.find_element_by_id("su").submit()
print(driver.title)
time.sleep(3)
# 主方法 测试方法执行入口
if __name__ == "__main__":
unittest.main()
-
创建步骤
-
1.定义一个类继承unittest.TestCase;
-
2.初始化测试固件;
setUp() 和 setDown() 是unittest框架中的测试固件
-
3.编写测试用例
测试用例的方法均以 test_ 开头,以test_开头命名的方法,是测试方法,在运行整个类的时候会默认执行;
-
4.编写主方法
unittest提供了全局的main()方法,使用它可以方便地将一个单元测试模块变成可以直接运行的测试脚本。main()方法搜索所有包含在该模块中以”test"命名的测试方法,并自动执行他们。
3.批量执行测试脚本
在上述测试测试脚本中,我们定义了三个测试用例,我们可以依次执行多个测试用例,当我们实际开发环境中针对不同功能在多个类中写了多个测试我们又该怎么一次执行所有测试脚本呢,测试就需要用到测试套件,测试套件可以批量执行多个类中的测试用例,下面演示使用:
- 定义两个测试类
测试1
import unittest
from selenium import webdriver
import time
# 定义一个类继承unittest.TestCase
class Baidu1(unittest.TestCase):
# 测试固件
# 环境的初始化
def setUp(self):
print("--setUp--")
# self 定义的是全局变量,否则只能在本方法内部使用
self.driver = webdriver.Chrome()
# 访问的URL
self.url = "https://www.baidu.com"
self.driver.maximize_window()
time.sleep(3)
# 测试环境的还原
def tearDown(self):
print("--tearDown--")
self.driver.quit()
# 测试用例 必须以test_开头 否则不识别,测试用例的执行顺序按照测试方法的字典序升序执行
def test_t1(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_link_text("hao123").click()
driver.implicitly_wait(3)
print(driver.title)
time.sleep(3)
# 注解:忽略测试用例的执行
@unittest.skip("skipping")
def test_t2(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_id("kw").send_keys("胡歌")
driver.find_element_by_id("su").submit()
time.sleep(3)
print(driver.title)
time.sleep(3)
def test_t3(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_id("kw").send_keys("刘浩存")
driver.find_element_by_id("su").submit()
print(driver.title)
time.sleep(3)
# 主方法 测试方法执行入口
if __name__ == "__main__":
unittest.main()
测试2
import unittest
from selenium import webdriver
import time
# 博客浏览的自动化测试
class Baidu2(unittest.TestCase):
# 测试固件
def setUp(self):
print("--setUp--")
self.driver = webdriver.Chrome()
self.url = "https://blog.youkuaiyun.com/qq_57549633"
self.driver.maximize_window()
time.sleep(3)
# 测试环境的还原
def tearDown(self):
print("--tearDown--")
self.driver.quit()
# 测试用例 必须以test_开头 否则不识别
def test_t1(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_xpath("//*[@id='userSkin']/div[2]/div/div[2]/div[1]/div[2]/div/div/div[1]/article/a/div[1]/h4").click()
driver.back()
time.sleep(3)
def test_t2(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_xpath("//*[@id='userSkin']/div[2]/div/div[2]/div[1]/div[2]/div/div/div[7]/article/a/div[1]").click()
driver.back()
time.sleep(3)
print(driver.title)
time.sleep(3)
def test_t3(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element_by_xpath("//*[@id='userSkin']/div[2]/div/div[2]/div[1]/div[2]/div/div/div[13]/article/a/div[1]/h4").click()
driver.back()
print(driver.title)
time.sleep(3)
# 主方法
if __name__ == "__main__":
unittest.main()
- addTest()方法
TestSuite类的addTest()方法可以把不同的测试类中的测试方法组装到测试套件中,但是addTest()一次只能把一个类里面的一个测试方法组装到测试套件中。方式如下:
将testbaidu1.py、testbaidu2.py中的测试方法放到一个测试套件中,在testsuite.py中实现。
import unittest
import TestBaidu1
import TestBaidu2
from selenium import webdriver
driver=webdriver.Chrome()
driver.get("https://www.baidu.com")
# 组织测试套件方法
def creatSuit():
# 1.把不同的测试脚本的类中的需要执行的测试方法放在一个测试套件中
suit=unittest.TestSuite()
suit.addTest(TestBaidu1.Baidu1("test_t1"))
suit.addTest(TestBaidu1.Baidu1("test_t2"))
suit.addTest(TestBaidu2.Baidu2("test_t3"))
return suit
# 主方法加载套件
if __name__=="__main__":
suite = creatSuit()
# verbosity 日志显示级别,分为 0 1 2
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
- makeSuite()和TestLoader()的应用
在unittest 框架中提供了makeSuite() 的方法,makeSuite可以实现把测试用例类内所有的测试case组成的测试套件TestSuite ,unittest 调用makeSuite的时候,只需要把测试类名称传入即可。
TestLoader 用于创建类和模块的测试套件,一般的情况下,使TestLoader().loadTestsFromTestCase(TestClass) 来加载测试类。
import unittest
import TestBaidu1
import TestBaidu2
from selenium import webdriver
driver=webdriver.Chrome()
driver.get("https://www.baidu.com")
# 组织测试套件方法
def creatSuit():
# 2.将一个测试脚本中的类中的所有方法都添加到套件中
suit=unittest.TestSuite()
suit.addTest(unittest.makeSuite(TestBaidu1.Baidu1))
suit.addTest(unittest.makeSuite(TestBaidu2.Baidu2))
# suite1 = unittest.TestLoader().loadTestsFromTestCase(testbaidu1.Baidu1)
# suite2 = unittest.TestLoader().loadTestsFromTestCase(testbaidu2.Baidu2)
# suite = unittest.TestSuite([suite1, suite2])
return suite
# 主方法加载套件
if __name__=="__main__":
suite = creatSuit()
# verbosity 日志显示级别,分为 0 1 2
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
- discover()的应用
discover 是通过递归的方式到其子目录中从指定的目录开始, 找到所有测试模块并返回一个包含它们对象的TestSuite ,然后进行加载与模式匹配唯一的测试文件,discover 参数分别discover(dir,pattern,top_level_dir=None)
import unittest
import TestBaidu1
import TestBaidu2
from selenium import webdriver
driver=webdriver.Chrome()
driver.get("https://www.baidu.com")
# 组织测试套件方法
def creatSuit():
# 3.将整个文件夹下所有的测试脚本的测试用例都添加到套件中
discover=unittest.defaultTestLoader.discover("../Test", pattern="TestBaidu*.py", top_level_dir=None)
return discover
# 主方法加载套件
if __name__=="__main__":
suite = creatSuit()
# verbosity 日志显示级别,分为 0 1 2
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
用例的执行顺序:unittest 框架默认加载测试用例的顺序是根据ASCII 码的顺序,数字与字母的顺序为: 09,AZ,a~z 。
所以, TestAdd 类会优先于TestBdd 类被发现, test_aaa() 方法会优先于test_ccc() 被执行。对于测试
目录与测试文件来说, unittest 框架同样是按照这个规则来加载测试用例。
4.unittest断言
自动化的测试中, 对于每个单独的case来说,一个case的执行结果中, 必然会有期望结果与实际结果, 来判断该case是通过还是失败, 在unittest 的库中提供了大量的实用方法来检查预期值与实际
值, 来验证case的结果。
下面是一些常用的断言:
代码演示:
import time
from selenium import webdriver
import unittest
# 定义一个类继承unittest.TestCase
class Test(unittest.TestCase):
# 测试固件
# 环境的初始化
def setUp(self):
print("--setUp--")
# self 定义的是全局变量,否则只能在本方法内部使用
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com"
self.driver.maximize_window()
time.sleep(3)
# 测试环境的还原
def tearDown(self):
print("--tearDown--")
self.driver.quit()
# 测试用例
def test_t1(self):
driver=self.driver
url=self.url
driver.get(url)
time.sleep(3)
driver.find_element_by_id("kw").send_keys("胡歌")
driver.find_element_by_id("su").click()
driver.implicitly_wait(5)
print(driver.title)
# self.assertEqual("胡歌_百度搜索", driver.title, msg="未打开网页")
self.assertTrue("百度一下,你就知道" == driver.title, msg="内容不相等")
time.sleep(5)
driver.quit()
5.HTML报告生成
脚本执行完毕之后,还需要看到HTML报告,下面我们就通过HTMLTestRunner.py 来生成测试报告。
HTMLTestRunner支持python2.7。python3可以参见http://blog.51cto.com/hzqldjb/1590802来进行修改。
HTMLTestRunner.py 文件,下载地址: http://tungwaiyip.info/software/HTMLTestRunner.html
下载后将其放在testcase目录中去或者放入…\Python27\Lib 目录下(windows)。
import HTMLTestRunner
import sys
import unittest
from selenium import webdriver
import os
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
# 组织测试套件的方法
def creatSuit():
# Test目录下所有的测试用例
discover=unittest.defaultTestLoader.discover("../Test", pattern="Test*.py", top_level_dir=None)
return discover
if __name__ == "__main__":
# 当前路径、 等同于./
curpath = sys.path[0]
# 取当前时间
now = time.strftime("%Y-%m-%d-%H %M %S", time.localtime(time.time()))
if not os.path.exists(curpath + '/resultreport'):
os.makedirs(curpath + '/resultreport')
# 文件名
filename = curpath + '/resultreport/' + now + 'resultreport.html'
with open(filename, 'wb') as fp:
# 出html报告
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'测试报告',description=u'用例执行情况',verbosity=2)
suite = creatSuit()
runner.run(suite)
在resultreport目录下生成的测试报告:
测试报告中会详细展示用例的执行情况(我这里测试用例全部通过):
5.异常捕捉与错误截图
用例不可能每一次运行都成功,肯定运行时候有不成功的时候。如果可以捕捉到错误,并且把错误截图保存,这将是一个非常棒的功能,也会给我们错误定位带来方便。
# -*- coding: utf-8 -*-
from selenium import webdriver
import unittest
import time
import os
class Baidu1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(30)
self.base_url = "http://www.baidu.com/"
self.verificationErrors=[]
self.accept_next_alert = True
def tearDown(self):
self.driver.quit()
self.assertEqual([],self.verificationErrors)
# 忽略此测试用例
@unittest.skip("skipping")
def test_baidusearch(self):
driver = self.driver
driver.get(self.base_url)
driver.find_element_by_id("kw").clear()
driver.find_element_by_id("kw").send_keys(u"大虞海棠")
driver.find_element_by_id("su").click()
time.sleep(6)
def test_baidu(self):
driver = self.driver
driver.get(self.base_url)
driver.maximize_window()
driver.find_element_by_id("kw").send_keys("向往的生活")
driver.find_element_by_id("su").click()
time.sleep(6)
print(driver.title)
try:
self.assertEqual(driver.title, u"百度一下,你就知道", msg="不相等")
except:
self.saveScreenShot(driver, "hao.png") # 出现异常就调用截图函数进行截图
time.sleep(3)
# 截图函数
def saveScreenShot(self, driver, file_name): # 参数:驱动,截图名字
if not os.path.exists("./image"):
os.makedirs("./image")
now = time.strftime("%Y%m%d-%H%M%S", time.localtime(time.time()))
driver.get_screenshot_as_file("./image/"+now+"-"+file_name)
time.sleep(3)
if __name__ == "__main__":
unittest.main()
上述代码中,异常信息被捕获,在image目录下生成错误截图:
6.数据驱动
当需要测试不同条件下的测试用例时,为提升测试效率就需要使用数据驱动DDT。python 的unittest 没有自带数据驱动功能。所以如果使用unittest,同时又想使用数据驱动,那么就可以使用DDT来完成。
# -*- coding: utf-8 -*-
from selenium import webdriver
import unittest
import time
from ddt import ddt, unpack, data, file_data
import sys, csv
# 获取文本文件方法
def getTxt(file_name):
# ([周迅,周迅_百度搜索],[张国荣,张国荣_百度搜索],[张一山,张一山_百度搜索])
rows = []
path = sys.path[0]
with open(path+'/data/'+file_name, 'rt') 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 Baidu1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(30)
self.base_url = "http://www.baidu.com/"
self.driver.maximize_window()
self.verificationErrors=[]
self.accept_next_alert = True
def tearDown(self):
self.driver.quit()
self.assertEqual([],self.verificationErrors)
# 获取json数据
# @file_data('test_baidu_data.json') ([周迅,周迅_百度搜索],[张国荣,张国荣_百度搜索],[张一山,张一三_百度搜索])
# 获取多个变量时需要使用 @unpack
# @data(['Lisa', u"Lisa_百度搜索"], [u"双笙", u"7887双笙_百度搜索"], [u"张一山",u"张一三_百度搜索"])
# @unpack
@unittest.skip("skipping")
@data("王凯", "Lisa", "特朗普", "蒋欣")
def test_baidu1(self, value):
driver = self.driver
driver.get(self.base_url + "/")
driver.maximize_window()
driver.find_element_by_id("kw").clear()
driver.find_element_by_id("kw").send_keys(value)
driver.find_element_by_id("su").click()
# time.sleep(6)
# self.assertEqual(driver.title, expected_value, msg="搜索结果和期望不一致!")
time.sleep(6)
# @unittest.skip("skipping")
# 获取文本数据 [周迅, 周迅_百度搜索], [张国荣, 张国荣111_百度搜索], [张一山,张一山_百度搜索]
# @data(['Lisa', u"Lisa_百度搜索"], [u"双笙", u"7887双笙_百度搜索"])
# @unpack
@data(*getTxt('test_baidu_data.txt'))
def test_baidu2(self, value, expected_value):
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_id("kw").clear()
driver.find_element_by_id("kw").send_keys(value)
driver.find_element_by_id("su").click()
driver.maximize_window()
time.sleep(6)
#判断搜索网页的title和我们期望的是否一致
self.assertEqual(expected_value, driver.title, msg="和预期搜索结果不一致!")
print(expected_value)
print(driver.title)
time.sleep(6)
if __name__ == '__main__':
unittest.main()