unittest
一、测试框架的作用
1.找到测试用例(根据默认的测试用例的规则)。
2.执行测试用例。
3.判断测试用例的结果。
4.生成测试报告。
二、基础概念
1、默认规则
导入unittest模块
新建一个类,必须继承unittest.TestCase
测试用例必须以test_开头
2、夹具
setUp/tearDown 在每个用例的前后执行
setUpClass/tearDownClass 在每个类的前后执行
setUpModule/tearDownModule 在每个模块的前后执行
3、断言
self.assertEqual()
4、失败用例重跑
不支持
5、参数化
DDT
三、unittest重要组件
TestCase测试用例:包下的模块下的类下的各个函数。
TestSuite测试套件:整理测试用例,形成一个集合。
TestFixtrue测试固件:如前后置等。
TestLoader测试加载器:加载测试用例套件或者测试用例。
TestRunner测试运行器:运行测试用例。
四、命令行方式运行测试用例
一个unittest脚本App_001.py:
import unittest
class AppMedia(unittest.TestCase):
def test_001(self):
print('001号单元')
def test_002(self):
print('002号单元')
使用命令行方式运行脚本:
#参数说明:
-m 以命令行的方式运行测试用例
-v 以详细的方式展示测试结果,如显示测试用例所属函数名、模块名、类名
-k 通过通配符匹配的方式去查找测试用例
注:命令行方式是unittest默认的执行方式
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest App_001
001号单元
.002号单元
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest App_001.AppMedia
001号单元
.002号单元
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest App_001.AppMedia.test_001
001号单元
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest -v App_001
test_001 (App_001.AppMedia) ... 001号单元
ok
test_002 (App_001.AppMedia) ... 002号单元
ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
(venv) PS D:\JetBrains\PycharmProjects\unittest项目> python -m unittest -v App_001 -k *_001
test_001 (App_001.AppMedia) ... 001号单元
ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
注:unittest的一个坑
在pycharm中,当鼠标放到位置1->右键->运行代码会执行所有测试用例(test_001、test_002)
在pycharm中,当鼠标放到位置2->右键->运行代码只会执行test_001测试用例
在pycharm中,当鼠标放到位置2->右键->运行代码只会执行test_002测试用例
五、unittest运行结果标记
.代表成功。如果加了-v,那么会变成OK
F代表用例执行失败
E代表用例执行出错,有异常抛出
s代表用例被跳过
六、测试用例的执行顺序
根据test_后面字符串首位字符的ASCII码大小排序(由小到大)来执行测试用例,首位字符相同则比较第二位字符的ASCII码,依次类推。
注:ord(value)是python的内置函数,作用是返回value的ASCII码
七、实际使用中unittest的运行方式
加载一个目录下所有测试用例。具体如下:将unittest.main()单独放到一个文件中,通过加载器将包下的所有模块加载到测试套件中,然后通过main运行这个测试套件
目录结构:
>testcase
App_001.py
Login_001.py
run.py
文件内容:
App_001.py
import unittest
class AppMedia(unittest.TestCase):
def test_001(self):
print('001号单元')
def test_002(self):
print('002号单元')
def test_003(self):
print('003号单元')
Login_001.py
import unittest
class LoginHome(unittest.TestCase):
def test_login_001(self):
print('登录001')
run.py
import unittest
if __name__ == '__main__':
#通过TestLoader(加载器)加载./testcase文件夹下所有以_001.py结尾的模块到测试套件中,测试套件命名为suite
suite=unittest.defaultTestLoader.discover('./testcase',pattern='*_001.py')
#执行测试套件suite中的测试用例
unittest.main(defaultTest='suite')
执行结果:
D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
001号单元
002号单元
003号单元
登录001
Process finished with exit code 0
八、unittest的运行原理
TestCase测试用例:包下的模块下的类下的各个函数。
TestSuite测试套件:整理测试用例,形成一个集合。
TestFixtrue测试固件:如前后置等。
TestLoader测试加载器:加载测试用例套件或者测试用例。
TestRunner测试运行器:运行测试用例。
注:可以通过读取Lib\unittest\main.py了解unittest的运行原理,在文件最后一行main = TestProgram,main相当于就是TestProgram类,解读TestProgram类中的各种配置如下:
1、测试加载器初始化
def __init__(self, module='__main__', defaultTest=None, argv=None,
testRunner=None, testLoader=loader.defaultTestLoader,
exit=True, verbosity=1, failfast=None, catchbreak=None,
buffer=None, warnings=None, *, tb_locals=False):
#参数说明:
module:测试用例所在的路径__mai__代表当前模块。
defaultTest:默认的待测试的测试用例或者测试套件的名称。
argv:接收外部传递给程序的参数。
testRunner:测试运行器。
testLoader:测试加载器。
exit:是否在测试用例结束之后退出程序。
verbosity:显示详细信息的程度。0只显示用例总数以及全局执行结果。1默认值,显示用例总数以及全局执行结果外,还显示一个标记(. F E S)。>=2显示用例总数以及全局执行结果外,显示详解结果。
2、通过测试加载器加载测试用例保存到self.test
def createTests(self, from_discovery=False, Loader=None):
if self.testNamePatterns:
self.testLoader.testNamePatterns = self.testNamePatterns
if from_discovery:
loader = self.testLoader if Loader is None else Loader()
self.test = loader.discover(self.start, self.pattern, self.top)
elif self.testNames is None:
self.test = self.testLoader.loadTestsFromModule(self.module)
else:
self.test = self.testLoader.loadTestsFromNames(self.testNames,
self.module)
3、创建文本测试运行器
def runTests(self):
if self.testRunner is None:
self.testRunner = runner.TextTestRunner
4、最后通过self.resut = testRunner.run(self.test)执行已经加载好的测试用例。并且把结果放到self.result
5、内部流程走完后,针对不同的IDE展示测试结果
九、夹具(前置后置)
setUp/tearDown 在每个用例的前后执行
setUpClass/tearDownClass 在每个类的前后执行,必须加装饰器@classmethod
setUpModule/tearDownModule 在每个模块的前后执行,使用较少
文件内容:
App_001.py
import unittest
class AppMedia(unittest.TestCase):
def setUp(self) -> None: #-> None表示python建议此函数返回值为None,可以删除这部分
print('在每一个测试用例之前执行:打开浏览器,加载网页')
def tearDown(self) -> None:
print('在每一个测试用例之后执行:关闭浏览器')
@classmethod #类的前后置必须加上@classmethod装饰器
def setUpClass(cls) -> None:
print('----------在每个类之前执行:初始化日志对象,创建数据库连接----------')
@classmethod
def tearDownClass(cls) -> None:
print('----------在每个类之后执行:销毁日志对象,销毁数据库连接----------')
def test_001(self):
print('001号单元')
def test_002(self):
print('002号单元')
def test_003(self):
print('003号单元')
其余文件内容不变
执行结果:
D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py
----------在每个类之前执行:初始化日志对象,创建数据库连接----------
在每一个测试用例之前执行:打开浏览器,加载网页
001号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
002号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
003号单元
在每一个测试用例之后执行:关闭浏览器
----------在每个类之后执行:销毁日志对象,销毁数据库连接----------
登录001
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
Process finished with exit code 0
十、夹具封装
文件内容:
my_unit.py
#在testcase目录下新建一个文件,专门存放前后置函数,前后置的类继承unittest.TestCase类
import unittest
class MyUnit(unittest.TestCase):
def setUp(self) -> None: #-> None表示python建议此函数返回值为None,可以删除这部分
print('在每一个测试用例之前执行:打开浏览器,加载网页')
def tearDown(self) -> None:
print('在每一个测试用例之后执行:关闭浏览器')
@classmethod #类的前后置必须加上@classmethod装饰器
def setUpClass(cls) -> None:
print('----------在每个类之前执行:初始化日志对象,创建数据库连接----------')
@classmethod
def tearDownClass(cls) -> None:
print('----------在每个类之后执行:销毁日志对象,销毁数据库连接----------')
APP_001.py
#不再继承unittest.TestCase类,改为继承MyUnit类
from testcase.my_unit import MyUnit
class AppMedia(MyUnit):
def test_001(self):
print('001号单元')
def test_002(self):
print('002号单元')
def test_003(self):
print('003号单元')
Login_001.py
#不再继承unittest.TestCase类,改为继承MyUnit类
from testcase.my_unit import MyUnit
class LoginHome(MyUnit):
def test_login_001(self):
print('登录001')
run.py
#执行函数内容不变
import unittest
if __name__ == '__main__':
#通过TestLoader(加载器)加载./testcase文件夹下所有以_001.py结尾的模块到测试套件中,套件命名为suite
suite=unittest.defaultTestLoader.discover('./testcase',pattern='*_001.py')
#执行suite测试套件中的测试用例
unittest.main(defaultTest='suite')
执行结果:
D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
----------在每个类之前执行:初始化日志对象,创建数据库连接----------
在每一个测试用例之前执行:打开浏览器,加载网页
001号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
002号单元
在每一个测试用例之后执行:关闭浏览器
在每一个测试用例之前执行:打开浏览器,加载网页
003号单元
在每一个测试用例之后执行:关闭浏览器
----------在每个类之后执行:销毁日志对象,销毁数据库连接----------
----------在每个类之前执行:初始化日志对象,创建数据库连接----------
在每一个测试用例之前执行:打开浏览器,加载网页
登录001
在每一个测试用例之后执行:关闭浏览器
----------在每个类之后执行:销毁日志对象,销毁数据库连接----------
Process finished with exit code 0
十一、跳过测试用例
在测试用例函数上添加“跳过”装饰器即可跳过此测试用例,有如下三种装饰器
@unittest.skip(reason='无条件跳过用例')
@unittest.skipIf(age <= 18,reason='如果年龄小于等于18,跳过用例')
@unittest.skipUnless(age > 18,reason='如果年龄不大于18,跳过用例')
十二、断言
unittest.TestCase.assertEqual() #等于
unittest.TestCase.assertNotEquals #不等于
unittest.TestCase.assertTrue() #为True
unittest.TestCase.assertIn() #属于
十三、生成HTML报告
十四、代码演示
目录结构:
>unittest项目
>Public
Base_Method.py
My_Unit.py
>testcase
Ecshop.py
run.py
文件内容:
Base_Method.py
import time
from selenium import webdriver
class BaseMethod():
"""基础方法"""
def open_browser(self):
"""打开浏览器"""
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(10) #隐式等待10秒
return self.driver
def load_url(self,url):
"""加载网页"""
self.driver.get(url)
self.driver.maximize_window() #最大化窗口
def switch_to_frame(self,frame_name):
"""进入其他框架"""
self.driver.switch_to.default_content() #退出当前框架
self.driver.switch_to.frame(frame_name) #进入新frame(框架)
def pitch_element(self,method,element):
"""定位元素封装"""
return self.driver.find_element(method,element)
def send_keys(self,method,element,value):
"""发送值"""
self.pitch_element(method,element).send_keys(value)
def click(self,method,element):
"""点击"""
self.pitch_element(method,element).click()
def close_browser(self):
"""关闭浏览器"""
time.sleep(10)
self.driver.close()
My_Unit.py
import unittest,time
from Pubilc.Base_Method import BaseMethod
class MyUnit(unittest.TestCase):
def setUp(self) -> None: #-> None表示python建议此函数返回值为None,可以删除这部分
print('在每一个测试用例之前执行:打开浏览器,加载网页')
self.bp=BaseMethod()
self.driver = self.bp.open_browser()
self.bp.load_url("http://192.168.0.248/ecshop/admin/privilege.php?act=login")
def tearDown(self) -> None:
print('在每一个测试用例之后执行:关闭浏览器')
time.sleep(5)
self.bp.close_browser()
# @classmethod #类的前后置必须加上@classmethod装饰器
# def setUpClass(cls) -> None:
# print('----------在每个类之前执行:初始化日志对象,创建数据库连接----------')
#
# @classmethod
# def tearDownClass(cls) -> None:
# print('----------在每个类之后执行:销毁日志对象,销毁数据库连接----------')
Ecshop.py
from selenium.webdriver.common.by import By
from Pubilc.My_Unit import MyUnit
class Ecshop(MyUnit):
def test_edit_product(self):
print("登录")
self.bp.send_keys(By.NAME, "username", value="admin") #输入用户名
self.bp.send_keys(By.NAME, "password", value="admin12345") #输入密码
self.bp.click(By.XPATH, "/html/body/form/div/div[2]/div[2]/div[4]/input") #“登录”按钮
print("进入商品管理")
self.bp.switch_to_frame("menu-frame") #进入左侧菜单框架
self.bp.click(By.XPATH, "//*[@id='menu-ul']/li[2]") #“商品管理”按钮
self.assertIn("admin",self.driver.page_source) #driver.page_source:页面HTML源码
print("更新商品信息")
self.bp.switch_to_frame("main-frame") #进入主菜单框架
self.bp.click(By.XPATH, "//*[@id='listDiv']/table[1]/tbody/tr[3]/td[13]/a[2]") #“编辑”按钮
self.bp.click(By.XPATH,"//*[@id='tabbody-div']/form/div/input[2]") #“确定”按钮
text = self.bp.pitch_element(By.XPATH,"//td[@style='font-size: 14px; font-weight: bold']").text
self.assertEqual(text,"编辑商品成功!")
run.py
import unittest
if __name__ == '__main__':
#通过TestLoader(加载器)加载./testcase文件夹下所有以_001.py结尾的模块到测试套件中,套件命名为suite
suite=unittest.defaultTestLoader.discover('./testcase',pattern='E*.py')
#执行suite测试套件中的测试用例
unittest.main(defaultTest='suite')
运行结果:
#执行run.py函数运行unittest框架中的Ecshop类下的test_edit_product用例
D:\JetBrains\PycharmProjects\unittest项目\venv\Scripts\python.exe D:\JetBrains\PycharmProjects\unittest项目\run.py
在每一个测试用例之前执行:打开浏览器,加载网页
登录
进入商品管理
更新商品信息
在每一个测试用例之后执行:关闭浏览器
.
----------------------------------------------------------------------
Ran 1 test in 49.691s
OK
Process finished with exit code 0
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统