数据驱动测试
1. DDT 数据驱动测试基本介绍
DDT官方文档
DDT 数据驱动测试装饰器,可实现多个参数作用于一个测试用例,从而生成多个不同的子用例,需要搭配unittest 使用
使用ddt好处: 提高代码复用性、降低测试数据和代码耦合度、便于用例管理
ddt 核心的方法:
@data
:包含与您想要传给测试用例的参数,一般是:字典、列表、元组等@file_data
:将从JSON或YAML文件加载测试数据 (.yml和.yaml结尾的文件会被当做yaml文件加载,其余的都会以json文件加载)@unpack
:通常data中的每个值将作为单个参数传递给测试方法。unpack会自动将元组和列表解压缩为多个参数,并将字典解压缩为多个关键字参数
2. @data使用示例
应用场景:测试过程相同,但随着参数的不同,预期结果不同,例如:
- 如同一个API,参数不同,响应数据也不一致
- 如登录显示菜单的功能,不同权限的用户登录,菜单展示的内容不同
2.1 元组
import unittest
from ddt import ddt, data, unpack, file_data
# 声明了ddt类装饰器
@ddt
class MyddtTest(unittest.TestCase):
def setUp(self):
print("setUp:每个用例执行前调用")
def tearDown(self):
print("tearDown:每个用例执行后调用")
@data(1, 2, 3)
def test_01(self, value): # value用来接受data的数据
print(value)
if __name__ == "__main__":
unittest.main()
执行结果:
setUp:每个用例执行前调用
1
tearDown:每个用例执行后调用
setUp:每个用例执行前调用
2
tearDown:每个用例执行后调用
setUp:每个用例执行前调用
3
tearDown:每个用例执行后调用
示例说明:
- MyddtTest虽然只有一个
test_01
,但是通过@data(1, 2, 3)
分别将参数传递给test_01
,test_01
会执行三次,每次入参分别是1、2、3
setUp
在每个测试用例执行前会自动执行、tearDown
在每次测试用例执行后会自动执行,此处仅做打印,为了说明test_01
被调用了三次
2.1 列表拆分&未拆分
import unittest
from ddt import ddt, data, unpack, file_data
# 声明了ddt类装饰器
@ddt
class MyddtTest(unittest.TestCase):
@data([4,5],[6,7],[9,10])
def test_02(self, value):
print("test_02未拆分:",value)
@data([4,5],[6,7],[9,10])
@unpack
def test_03(self, value1,value2):
print("test_03已拆分:", value1,value2)
if __name__ == "__main__":
unittest.main()
执行结果
test_02未拆分: [4, 5]
test_02未拆分: [6, 7]
test_02未拆分: [9, 10]
test_03已拆分: 4 5
test_03已拆分: 6 7
test_03已拆分: 9 10
示例说明:
test_02
没有对数据拆分,因此入参是@data
中可遍历的3组数据,因此test_02
会执行3次test_03
有对数据拆分,因此入参是@data
中可遍历的3组数据,因此test_03
会执行3次,因为使用了@unpack
,每组还会被二次拆分
2.3 字典拆分&未拆分
import unittest
from ddt import ddt, data, unpack, file_data
# 声明了ddt类装饰器
@ddt
class MyddtTest(unittest.TestCase):
@data({'name': 'zs', 'age': 18},{'name': 'ls', 'age': 19})
def test_04(self, value):
print("test_04未拆分:", value)
@data({'name': 'zs', 'age': 18},{'name': 'ls', 'age': 19})
@unpack
def test_05(self, name,age):
print("test_05已拆分:", f"name:{name},age:{age}")
# test_06 与 test_05 用法等效,只是将数据存放在一个变量中
# *testdata 在这里的作用是将 testdata 列表中的每个字典作为单独的参数传递给 data 装饰器。这使得 data 装饰器能够接收多个参数,而不是一个单一的列表参数。
testdata = [{'name': 'zs', 'age': 18}, {'name': 'ls', 'age': 19}]
@data(*testdata)
@unpack
def test_06(self, name, age): #字典拆分需要与key的个数对应,否则会报错
print("test_06已拆分:", f"name:{name},age:{age}")
if __name__ == "__main__":
unittest.main()
执行结果:
test_04未拆分: {'name': 'zs', 'age': 18}
test_04未拆分: {'name': 'ls', 'age': 19}
test_05已拆分: name:zs,age:18
test_05已拆分: name:ls,age:19
test_06已拆分: name:zs,age:18
test_06已拆分: name:ls,age:19
3. @file_data 使用示例
3.1 @file_data加载json文件
login_user.json
{
"user1": {
"name": "admin",
"password": "123456"
},
"user2": {
"name": "guest",
"password": "1122223"
}
}
MyddtTest.py
示例加载json文件并作用于testcase的两种方式
import unittest
from ddt import ddt, data, unpack, file_data
# 声明了ddt类装饰器
@ddt
class MyddtTest(unittest.TestCase):
@file_data("login_user.json")
def test_07(self, name, password):
print("test_07:", f"name={name},password={password}")
@file_data("login_user.json")
def test_08(self, **login_info):
print("test_08:", f"name={login_info.get('name')},password={login_info.get('password')}")
if __name__ == "__main__":
unittest.main()
执行结果: 可见两种写法效果是一样的
test_07: name=admin,password=123456
test_07: name=guest,password=1122223
test_08: name=admin,password=123456
test_08: name=guest,password=1122223
3.2 @file_data加载yaml文件
# config.yaml
# 使用-分隔用例,则yaml读取到的数据类型为列表
-
model: 登录模块
title: 登录成功
url: http://api.test.cn/api/user/user_login
method: POST
data:
username: admin
pwd: Ace123456
check:
error_code: 200
msg: 登录成功!
-
model: 登录模块
title: 密码错误
url: http://api.test.cn/api/user/user_login
method: POST
data:
username: admin
pwd: Ace123456!!
check:
error_code: 400
msg: 密码错误!
import unittest
from ddt import ddt, data, unpack, file_data
# 声明了ddt类装饰器
@ddt
class MyddtTest(unittest.TestCase):
# @file_data加载yaml文件
@file_data("config.yaml")
def test_10(self, model, title, url, method, data, check):
username = data['username']
pwd = data['pwd']
print(model, title, url, username, pwd, check)
# **testdata:将提取到的数据存放在空字典testdata中
@file_data("config.yaml")
def test_11(self, **testdata):
# 再从字典testdata中单独提取参数
model = testdata['model']
title = testdata['title']
url = testdata['url']
username = testdata['data']['username']
pwd = testdata['data']['pwd']
check = testdata['check']
print(model, title, url, username, pwd, check)
if __name__ == "__main__":
unittest.main()
注:
使用ddt遇到的坑: 运行程序时将鼠标定位在类或者程序外运行解决,如果放在代码中间位置,如下代码,若鼠标放在第一行代码后面,点击run
的时候会报错type object 'MyddtTest' has no attribute 'test_04'
@data({'name': 'zs', 'age': 18},{'name': 'ls', 'age': 19})
def test_04(self, value):
print("test_04未拆分:", value)
找到的原因是: 运行程序时将鼠标定位在类或者程序外运行解决,ddt只能运行整体,不能运行局部即单个测试方法
若有其他更合理的解释欢迎留言指导