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

 

posted @   天才九少  阅读(134)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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 热点速览」
点击右上角即可分享
微信分享提示