python单元测试框架unittest的扩展库DDT
正文
- DDT三种参数化方式
- DDT 支持数据文件的参数化
- ddt+excel读取测试数据
介绍
DDT(Data-Driven Tests)允许使用不同 的测试数据来运行一个测试用例,并将其展示为多个测试用例。
GitHub 地址:
https://github.com/datadriventests/ddt
pip install ddt
ddt本质其实就是装饰器,一组数据一个场景。
ddt模块包含了一个类的装饰器ddt(@ddt)和三个方法的装饰器(@data、@unpack、@file_data),其中:
- @data:包含多个你想要传给测试用例的参数,可以为列表、元组、字典等;
- @file_data:会从json或yaml中加载数据;(注意,如果文件以”.yml”或者”.yaml”结尾,ddt会作为yaml类型处理,其他所有文件都会作为json文件处理。如txt文件)
- @unpack:分割元素。(需要搭配unittest测试框架使用,实现数据驱动测试)
DDT三种参数化方式
1,测试类需要通过@ddt 装饰器进行装饰
2,DDT参数化方式:列表、元组、字典
import unittest from time import sleep from selenium import webdriver from selenium.webdriver.common.by import By from ddt import ddt, data, file_data, unpack @ddt # 类前面是ddt,测试函数前面是data class TestBaidu(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.driver = webdriver.Chrome() cls.base_url = 'https://www.baidu.com' @classmethod def tearDownClass(cls) -> None: cls.driver.quit() def baidu_search(self, search_key): self.driver.get(self.base_url) self.driver.find_element(By.ID, 'kw').send_keys(search_key) self.driver.find_element(By.ID, 'su').click() sleep(2) # 参数化方式一 @data(['case1', 'selenium'], ['case2', 'ddt'], ['case3', 'python']) @unpack # @unpack拆分,相当于把数据的最外层结构去掉 def test_search1(self, case, search_key): print('第一组测试用例:', case) self.baidu_search(search_key) self.assertEqual(self.driver.title, search_key + '_百度搜索') # 参数化方式二 @data(('case1', 'selenium'), ('case2', 'ddt'), ('case3', 'python')) @unpack def test_search2(self, case, search_key): print('第二组测试用例:', case) self.baidu_search(search_key) self.assertEqual(self.driver.title, search_key + '_百度搜索') # 参数化方式三 @data({"search_key": "selenium"}, {"search_key": "ddt"}, {"search_key":"python"}) @unpack def test_search3(self, search_key): # 字典的 key 与测试方法的参数要保持一致 print('第二组测试用例:', search_key) self.baidu_search(search_key) self.assertEqual(self.driver.title, search_key + '_百度搜索') if __name__ == '__main__': unittest.main(verbosity=2)
@unpack使用详情
import unittest from ddt import ddt, data, unpack, file_data # 声明了ddt类装饰器 @ddt class MyddtTest(unittest.TestCase): # @data方法装饰器 # 单组元素 @data(1,2,3) def test_01(self, value): # value用来接受data的数据 print(value) # 多组数据,未拆分 @data([1,2],[3,4]) def test_02(self, value): print(value) # 多组数据,拆分 # @unpac拆分,相当于把数据的最外层结构去掉 @data([5,6],[7,8]) @unpack def test_03(self, value1, value2): print(value1, value2) # 单个列表字典,未拆分 @data([{"name": "peter", "age": 15, "addr": "chengdu"}]) def test_04(self, value): print(value) # 多个列表字典,拆分 @data([{"name":"peter","age":16,"addr":"chengdu"},{"name":"lily","age":17,"addr":"chengdu"}]) @unpack def test_05(self, value1, value2): print(value1, value2) # 单个字典,拆分 # @data里的数据key必须与字典的key保持一致 @data({"name":"jack","age":20}) @unpack def test_06(self, name, age): print(name, age) # 多个字典, 拆分 @data({"name":"peter","age":18,"addr":"chengdu"},{"name":"lily","age":19,"addr":"chengdu"}) @unpack def test_07(self, name, age, addr): print(name, age, addr) # 多个列表字典,引用数据 testdata = [{"name": "peter", "age": 21, "addr": "chengdu"}, {"name": "lily", "age": 22, "addr": "chengdu"}] @data(testdata) @unpack def test_08(self, value1, value2): print(value1, value2) # @data(*testdata):*号意为解包,ddt会按逗号分隔,将数据拆分(不需要@unpack方法装饰器了) testdata = [{"name":"peter","age":23,"addr":"chengdu"},{"name":"lily","age":24,"addr":"chengdu"}] @data(*testdata) def test_09(self, value): print(value)
if __name__ == "__main__": unittest.main()
DDT 支持数据文件的参数化
- 支持json文件
- 支持yaml文件
创建 ddt_data_file.json
{ "case1": {"search_key": "python"}, "case2": {"search_key": "ddt"}, "case3": {"search_key": "Selenium"} }
使用 test_data_file.json 文件参数化测试用例
# 参数化读取 JSON 文件 @file_data('ddt_data_file.json') def test_search4(self, search_key): print("第四组测试用例:", search_key) self.baidu_search(search_key) self.assertEqual(self.driver.title, search_key + "_百度搜索")
示例二
创建config.json
{ "stu1": { "name": "Peter", "age": 29, "addr": "BeiJing" }, "stu2": { "name": "Jack", "age": 30, "addr": "ShenZhen" } }
使用json文件参数化
# 声明了ddt类装饰器 @ddt class MyddtTest(unittest.TestCase): # @file_data加载json文件 # **testdata:将提取到的数据存放在空字典testdata中 @file_data("config.json") def test_10(self, **testdata): # 再从字典testdata中单独提取参数 name = testdata['name'] age = testdata['age'] addr = testdata['addr'] print(testdata) print(name, age, addr) # 直接提取参数, test()方法中的参数必须与json文件中的键保持一致 @file_data("config.json") def test_11(self,name, age, addr): name = name age = age addr = addr print(name, age, addr)
------------------------------------------
创建ddt_data_file.yaml 文件
case1: - search_key: "python" case2: - search_key: "ddt" case3: - search_key: "unittest"
使用ddt_data_file.yaml 文件参数化测试用例
@file_data('ddt_data_file.yaml') def test_search5(self, case): search_key = case[0]["search_key"] print("第五组测试用例:", search_key) self.baidu_search(search_key) self.assertEqual(self.driver.title, search_key + "_百度搜索")
示例二
创建config.yaml
# 使用-分隔用例,则yaml读取到的数据类型为列表 - model: 注册模块 title: 注册成功 url: http://api.nnzhp.cn/api/user/user_reg method: POST data: username: yingcr10 pwd: Ace123456 cpwd: Ace123456 check: error_code: 0 msg: 注册成功! - model: 注册模块 title: 用户名长度小于6位,注册失败 url: http://api.nnzhp.cn/api/user/user_reg method: POST data: username: yingc pwd: Ace123456 cpwd: Ace123456 check: error_code: 3002
使用yaml文件
@ddt class MyddtTest(unittest.TestCase): # @file_data加载yaml文件 @file_data("config.yaml") def test_12(self, model, title, url, method, data, check): username = data['username'] pwd = data['pwd'] cpwd = data['pwd'] print(model, title, url, method, data, check) print(username, pwd, cpwd) # **testdata:将提取到的数据存放在空字典testdata中 @file_data("config.yaml") def test_13(self, **testdata): # 再从字典testdata中单独提取参数 model = testdata['model'] title = testdata['title'] print(testdata) print(model, title)
ddt+excel读取测试数据
先从excel文件中读取数据,然后再用ddt加载已读取的数据
处理excel文件并返回列表
from openpyxl import load_workbook class ExcelData(): def __init__(self, file="config.xlsx"): '''初始化Excel对象''' self.file = file self.wb = load_workbook(self.file) def get_row_value(self, row, sheet_name="Sheet1"): '''获取Excel中某一行的数据''' sh = self.wb[sheet_name] max_col = sh.max_column row_value = [] for col in range(1, max_col+1): value = sh.cell(row, col).value row_value.append(value) return row_value def get_all_row(self, sheet_name="Sheet1"): '''获取Excel中所有行的数据,并存放在列表中''' sh = self.wb[sheet_name] max_row = sh.max_row row_value = [] for row in range(2, max_row+1): value = self.get_row_value(row) row_value.append(value) return row_value if __name__ == "__main__": excel = ExcelData() testdata = excel.get_all_row() print(testdata)
使用列表数据
import requests import unittest from ddt import ddt, data, unpack, file_data from get_excel import ExcelData @ddt class SignTest(unittest.TestCase): # 从get_excel.py中读取测试数据 excel = ExcelData() testdata = excel.get_all_row() @data(*testdata) def test_sign(self, datas): # 由于从excel中读取到的数据为列表形式,所以采用下标来提取各参数 ID = datas[0] model = datas[1] title = datas[2] method = datas[3] url = datas[4] username = datas[5] pwd = datas[6] cpwd = datas[7] check = datas[8] body = { "username": username, "pwd": pwd, "cpwd": cpwd } self.sign_test(ID,model,title,url,method,body,check) def sign_test(self,ID,model,title,url,method,body,check): print("用例ID:", ID) print("模块:", model) print("用例标题:", title) response = requests.request(url=url, method=method, data=body).text try: # 通过断言,比较实际结果是否与预期结果一致 # 由于从excel中读取到的check为str类型,所以response不用转换为dict,直接断言比较是否相等 assert check == response print("测试通过") except Exception as e: print("测试失败") raise e if __name__ == "__main__": unittest.main()
参考学习:
https://www.cnblogs.com/Maruying/p/13516791.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」