unittest
1. 基础
unittest —— 单元测试框架,这里的单元测试指的是对最小的软件设计单元(模块)进行验证,在UI自动化测试里面,单元测试主要针对UI界面的功能进行自动化测试
unittest 是python 的单元测试框架,它主要有以下作用:
- 提供用例组织与执行
- 提供丰富的比较方法
- 提供丰富的日志
unittest 四个重要概念:
- Test Fixture:对一个测试用例环境的搭建和销毁,就是一个fixture,通过覆盖 setUp() 和 tearDown() 方法来实现
setUp() 方法可以进行初始化,比如测试环境的搭建,获取待测试浏览器的驱动
tearDown() 方法进行环境的销毁,可以进行关闭浏览器,关闭数据库连接,清除数据库中产生的数据等操作 - Test Case:一个 TestCase 的实例就是一个测试用例。单元测试的本质就在这里,一个测试用例就是一个完整的测试单元,可以对某一个功能进行验
证 - Test Suite:一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这个就产生了测试套件TestSuite的概念
- Test Runner:在unittest框架中,通过 TextTestRunner 类提供的 run() 方法来执行 test suite/test case
2. 四个重要概念
1. TestFixture
TestFixture :对一个测试用例环境的搭建和销毁,就是一个fixture,通过覆盖 setUp() 和 tearDown() 方法来实现
1.1 setUp() 和 tearDown()
from selenium.webdriver.common.by import By
from selenium import webdriver
import unittest
import time
'''
TestFixture: setUp和tearDown
setUp()方法: 主要是用来初始化测试环境, 它在每条测试用例执行前都会调用
tearDown()方法:主要作用是测试用例执行完毕后恢复测试环境, 即使出现异常也会调用此方法,每条用例执行结束后都会运行
'''
class Test(unittest.TestCase):
def setUp(self):
print("-----setUp:初始化测试环境-----")
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
time.sleep(3)
def test_baidu01(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element(By.LINK_TEXT, "hao123").click()
time.sleep(4)
def test_baidu02(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element(By.LINK_TEXT, "新闻").click()
time.sleep(4)
def tearDown(self):
print("-----tearDown:测试用例执行完毕后恢复测试环境-----")
self.driver.quit()
import unittest
class Test(unittest.TestCase):
def setUp(self): # 调用setUp
super().setUp()
print("用例执行前操作")
def test_add01(self):
print(2+2)
def test_add02(self):
print(3+3)
def tearDown(self): # 调用tearDown
super().tearDown()
print("用例执行后操作")
1.2 setUpClass() 和 tearDownClass()
from selenium.webdriver.common.by import By
from selenium import webdriver
import unittest
import time
'''
setUpClass: 初始化测试环境且只会执行一次。在类中需要加上@classmethod
tearDownClass: 恢复测试环境且只会执行一次。在类中需要加上@classmethod
'''
class Test(unittest.TestCase):
@classmethod
def setUpClass(self):
print("-----setUp:初始化测试环境-----")
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
time.sleep(3)
def test_baidu01(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element(By.LINK_TEXT, "hao123").click()
time.sleep(4)
def test_baidu02(self):
driver = self.driver
url = self.url
driver.get(url)
driver.find_element(By.LINK_TEXT, "新闻").click()
time.sleep(4)
@classmethod
def tearDownClass(self):
print("-----tearDown:测试用例执行完毕后恢复测试环境-----")
self.driver.quit()
import unittest
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
print("用例执行前操作")
@classmethod
def tearDownClass(cls):
super().tearDownClass()
print("用例执行后操作")
def test_add01(self):
print(2+2)
def test_add02(self):
print(3+3)
2. TestCase
TestCase 指的就是测试用例
TestCase编写:
- 测试类必须继承 unittest.TestCase
- 测试方法名称命名必须以 test_ 开头
- 测试方法的执行顺序由 Case序号 决定, 并非由代码顺序决定
# 1. 导入unittest
import unittest
'''
TestCase
'''
# 2. 创建类继承unittest.TestCase
class Test(unittest.TestCase):
# 3. 创建测试用例方法, 方法要以 test_ 开头
# 执行顺序根据case序号决定, 并非代码的顺序 0~9 A~Z a~z
def test_add01(self):
print(2 + 2)
def test_add02(self):
print(3 + 3)
3. TestSuite
TestSuite(测试套件): 可以组织多个测试用例
先准备两个测试用例:test16001 和 test16002
# test16001
import unittest
class Test01(unittest.TestCase):
def setUp(self): # 调用setUp
super().setUp()
print("用例执行前操作")
def test_add01(self):
print(1+1)
def test_add02(self):
print(2+2)
def tearDown(self): # 调用tearDown
super().tearDown()
print("用例执行后操作")
# test16002
import unittest
class Test02(unittest.TestCase):
def setUp(self): # 调用setUp
super().setUp()
print("用例执行前操作")
def test_add03(self):
print(3+3)
def test_add04(self):
print(4+4)
def tearDown(self): # 调用tearDown
super().tearDown()
print("用例执行后操作")
3.1 addTest()
把不同测试脚本的类中需要执行的方法放在一个测试套件中
import unittest
from Selenium学习 import test16001
from Selenium学习 import test16002
def creatSuit():
# 把不同测试脚本的类中需要执行的方法放在一个测试套件中
# addTest
suite = unittest.TestSuite()
suite.addTest(test16001.Test01("test_add01"))
suite.addTest(test16002.Test02("test_add04"))
return suite
if __name__ == "__main__":
suit = creatSuit()
runner = unittest.TextTestRunner()
runner.run(suit)
3.2 makeSuit()
把一个测试脚本中所有的测试用例都添加到测试套件中
import unittest
from Selenium学习 import test16001
from Selenium学习 import test16002
def creatSuit():
# 把一个测试脚本中所有的测试用例都添加到测试套件中
# makeSuit
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(test16001.Test01))
suite.addTest(unittest.makeSuite(test16002.Test02))
return suite
if __name__ == "__main__":
suit = creatSuit()
runner = unittest.TextTestRunner()
runner.run(suit)
3.3 TestLoader()
把一个测试脚本中所有的测试用例都添加到测试套件中
import unittest
from Selenium学习 import test16001
from Selenium学习 import test16002
def creatSuit():
# 把一个测试脚本中所有的测试用例都添加到测试套件中
# TestLoader
suit1 = unittest.TestLoader().loadTestsFromTestCase(test16001.Test01)
suit2 = unittest.TestLoader().loadTestsFromTestCase(test16002.Test02)
suite = unittest.TestSuite([suit1, suit2])
return suite
if __name__ == "__main__":
suit = creatSuit()
runner = unittest.TextTestRunner()
runner.run(suit)
3.4 discover
把一个文件夹下面所有的测试脚本中的测试用例放入测试套件
import unittest
from Selenium学习 import test16001
from Selenium学习 import test16002
'''
有多个测试文件时:
1. suite = unittest.TestLoader().discover("指定搜索的目录文件","指定字母开头模块文件")
2. suite = unittest.defaultTestLoader.discover("指定搜索的目录文件","指定字母开头模块文件") 【推荐】
# unittest.defaultTestLoader, 使用discover()去加载测试用例
# 找到指定目录下所有测试模块,并可递归查到子目录下的测试模块,只有匹配到文件名才能被加载
discover(start_dir, pattern=‘test*.py’, top_level_dir=None)
start_dir:要测试的模块名 或 测试用例目录
pattern=“test*.py”: 表示用例文件名的匹配原则。 此处匹配文件名以“test”开头的“.py”类型的文件,*表示任意字符
top_level_dir=None:测试模块的顶层目录, 如果没有顶层目录, 默认为None
'''
def creatSuit():
# 把一个文件夹下面所有的测试脚本中的测试用例放入测试套件
# discover 正则表达式匹配
discover = unittest.defaultTestLoader.discover("../Selenium学习", pattern="test1600*.py", top_level_dir=None)
return discover
if __name__ == "__main__":
suit = creatSuit()
runner = unittest.TextTestRunner()
runner.run(suit)
4. TestRunner
TextTestRunner 测试用例运行器:
run() 方法是测试用例的执行, 入参为suite测试套件
import unittest
class Test(unittest.TestCase):
def test_add01(self):
print(1+1)
def test_add02(self):
print(2+2)
if __name__ == '__main__':
# 实例化TestSuite
suite = unittest.TestSuite()
# 添加测试用例
suite.addTest(Test("test_add01"))
suite.addTest(Test("test_add02"))
# 实例化TextTestRunner
runner = unittest.TextTestRunner()
# 传入suite并执行测试用例
runner.run(suite)
3. 忽略测试用例的执行
在遇到不想执行的测试用例时,可以使用skip方法
- unittest.skip(reason) :无条件跳过用例, reason是说明原因
- unittest.skipIf(condition, reason):condition为true时跳过用例
- unittest.skipUnless(condition, reason):condition为False的时候跳过
import unittest
class Test(unittest.TestCase):
@classmethod
def setUpClass(self):
super().setUpClass()
print("测试前的操作")
@classmethod
def tearDownClass(self):
super().tearDownClass()
print("测试后的操作")
@unittest.skip("无条件跳过")
def test_01(self):
self.assertIn("a","aa")
@unittest.skipIf(1 < 2, "1<2 为真,跳过")
def test_02(self):
self.assertIn("a","aa")
@unittest.skipUnless(1 > 2, "1>2为假,跳过")
def test_03(self):
self.assertNotIn("a","bb")
def test_04(self):
self.assertNotIn("a","bb")
4. 断言
1. 什么是断言:
让程序代替人工自动的判断预期结果和实际结果是否相符
断言的结果:
①. True,用例通过
②. False,代码抛出异常,用例不通过
③. 在unittest中使用断言,需要通过 self.断言 方法
2. 为什么要断言:
自动化脚本执行时都是无人值守,需要通过断言来判断自动化脚本的执行是否通过
3. 常用的断言:
方法 | 检查 |
---|---|
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertTrue(x) | x的布尔值为真 |
assertFalse(x) | x的布尔值为假 |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
import unittest
class Test(unittest.TestCase):
def setUp(self):
super().setUp()
print("测试开始")
def test_01(self):
print("1 == 1")
self.assertEqual(1, 1) # 成功
def test_02(self):
print("1 != 2")
self.assertNotEqual(1, 2) # 成功
def test_03(self):
print("1 < 2")
self.assertTrue(1 < 2) # 成功
def test_04(self):
print("1 > 2")
self.assertFalse(1 > 2) # 成功
def test_05(self):
print("a in aa")
self.assertIn("a", "aa") # 成功
def test_06(self):
print("a not in bb")
self.assertNotIn("a", "bb") # 成功
def tearDown(self) -> None:
super().tearDown()
print("测试结束")
5. HTML 报告生成
- 首先要在 Python\Python310\Lib 下放入 HTMLTestRunner.py
- 报告生成步骤:
解决 HTML 文件存放问题
解决 HTML 命名问题
生成报告
import HTMLTestRunner
import os
import sys
import time
import unittest
def createsuite():
discovers = unittest.defaultTestLoader.discover("../Selenium学习", pattern="demo00*.py", top_level_dir=None)
print(discovers)
return discovers
if __name__=="__main__":
# 文件夹的创建
curpath = sys.path[0]
print(sys.path)
print(sys.path[0])
# 创建文件夹,当前路径下创建
if not os.path.exists(curpath+'/resultReport'):
os.makedirs(curpath+'/resultReport')
# 文件夹的命名,不能让名称重复
# 时间 时分秒
now = time.strftime("%Y-%m-%d-%H %M %S", time.localtime(time.time()))
print(now)
print(time.time())
print(time.localtime(time.time()))
# 文件名
filename = curpath + '/resultReport/'+ now + 'resultReport.html'
with open(filename, 'wb') as fp:
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title="测试报告",
description="用例执行情况", verbosity=2)
suite = createsuite()
runner.run(suite)
6. 数据驱动(unittest ddt)
用数据驱动测试用例不断的执行,直至所有测试数据执行完
数据驱动的特点:
- 代码和数据分离,避免代码冗余
- 不需要重复的代码逻辑
- 首先需要在 pycharm 底部打开 Terminal ,输入 pip install ddt
- 使用数据驱动,要在class前加上修饰器 @ddt
from selenium.webdriver.common.by import By
from selenium import webdriver
from ddt import ddt, unpack, data, file_data
import unittest
import time
import json
'''
# 使用数据驱动,要在class前加上修饰器 @ddt
# @ddt
class TestBaidu(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.get('https://www.baidu.com/')
def tearDown(self):
time.sleep(3)
self.driver.quit()
# 单一参数
@data('杨幂', '杨洋')
def test_01(self, name):
self.driver.find_element(By.ID,'kw').send_keys(name)
self.driver.find_element(By.ID,'su').click()
'''
'''
# 使用数据驱动,要在class前加上修饰器 @ddt
@ddt
class TestDemo(unittest.TestCase):
# 多参数数据驱动
@data(['张三', '94'],['李四','82'],['王五','86'])
# unpack 是进行拆包,将列表中的两个数据分别传入对应的变量中
@unpack
def test_2(self, name, score):
print( name + ' : '+ score)
'''
'''
# split():分割,返回列表
# strip():去掉两边的字符或者字符串,默认删除空白符(包括'\n', '\r', '\t', ' ')
def readTxt():
rows = []
with open('C:\\Users\\Jchent\\Desktop\\test1.txt', 'rt', encoding='utf-8') as f:
for line in f.readlines():
rows.append(line.strip('\n').split(','))
return rows
class testDemo(unittest.TestCase):
def test_01(self):
txt = readTxt()
print(txt)
'''
# 用json多个参数读取
def reads_score():
with open('C:\\Users\\Jchent\\Desktop\\test_Data.json', encoding='utf-8') as f:
result = json.load(f) # 列表
return result
@ddt
class TestDemo(unittest.TestCase):
# 多参数数据驱动
@data(*reads_score())
# unpack 是进行拆包,将列表中的两个数据分别传入对应的变量中
@unpack
def test_2(self, name, score):
print(name + ' : '+ score)
if __name__ == '__main__':
unittest.main()
7. 截图操作
用例不可能每一次运行都成功,肯定运行时候有不成功的时候。如果可以捕捉到错误,并且把错误截图保存,将会给我们错误定位带来方便
截图方法:driver.get_screenshot_as_file()
# 截图函数
def errorScreenShot(self, driver, file_name): # 参数:驱动,截图名字
if not os.path.exists("./demoImage"):
os.makedirs("./demoImage")
now = time.strftime("%Y%m%d-%H%M%S", time.localtime(time.time()))
driver.get_screenshot_as_file("./demoImage/" + now + "-" + file_name)
time.sleep(3)
from selenium import webdriver
from selenium.webdriver.common.by import By
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)
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(3)
print(driver.title)
try:
self.assertEqual(driver.title, "百度一下,你就知道", msg="不相等")
except:
self.errorScreenShot(driver, "error.png") # 出现异常就调用截图函数进行截图
time.sleep(5)
# 截图函数
def errorScreenShot(self, driver, file_name): # 参数:驱动,截图名字
if not os.path.exists("./demoImage"):
os.makedirs("./demoImage")
now = time.strftime("%Y%m%d-%H%M%S", time.localtime(time.time()))
driver.get_screenshot_as_file("./demoImage/" + now + "-" + file_name)
time.sleep(3)
if __name__ == "__main__":
unittest.main()