ddt数据驱动

yaml安装使用

PyYaml安装

pip install PyYaml

yaml使用

导包: import yaml

yaml语法格式

它的基本语法规则如下

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时可以使用Tab键,也可以使用空格
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

YAML 支持的数据结构有三种:

  • 字典:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 列表:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  • 纯量(scalars):单个的、不可再分的值

字典

1
2
3
4
animal: pets

#或者如下格式
hash: { name: Steve, foo: bar }

列表

1
2
3
4
5
6
- Cat
- Dog
- Goldfish

#或者如下格式
animal: [Cat, Dog]

字典套列表

1
2
3
4
5
6
name: 小明
age: 20
languages:
- Java
- JavaScript
- Python

列表套字典

1
2
3
4
5
6
7
-
- Java
- JavaScript
- Python
-
name: 小明
age: 20

读取yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
import yaml

file = open('yml文件路径/文件名', 'r', encoding='utf-8')
content = yaml.load(stream=file, Loader=yaml.FullLoader)

print(content)
print(type(content))

for i in content:
print(i)

file.close()

open() 方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError。

注意:

  • 使用 open() 方法一定要保证关闭文件对象,即调用 close() 方法
  • open() 函数常用形式是接收两个参数:文件名(file)和模式(mode)
1
2
# 完整语法格式
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

参数说明:

  • file: 必需,文件路径(相对或者绝对路径)。
  • mode: 可选,文件打开模式
  • buffering: 设置缓冲
  • encoding: 一般使用utf8
  • errors: 报错级别
  • newline: 区分换行符
  • closefd: 传入的file参数类型
  • opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。

mode参数常用有:

ddt安装和使用

ddt数据驱动安装

pip intall ddt

ddt数据驱动使用

导包:from ddt import ddt, data, unpack, file_data

ddt数据驱动介绍

DDT包含类的装饰器@ddt和两个方法装饰器@data(直接输入测试数据)和@file_data(文件路径/文件名)

@ddt:

  • 类装饰器,用于TestCase的子类test方法。

@data:

  • @data是将内容依照逗号进行解析,解析后有多少组数据就执行多少次用例。

  • 如果数据是元组,列表,字典等格式,需要自行在脚本中对数据进行分解或者使用@unpack分解数据。

@unpack:

  • 额外的装饰器,自动将元组和列表解压缩为多个参数,并将字典解压缩为多个关键字参数。

@file_data:

  • 方法装饰器(数据),将从JSON或YAML文件加载测试数据。

  • 只有以“.yml”或“.yaml”结尾的文件才会作为YAML文件加载。所有其他文件都作为JSON文件加载。

@data和@unpack的使用

基本数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
import unittest
from ddt import ddt, data

@ddt()
class Test_ddt(unittest.TestCase):
@data(1, 'name')
def test_01(self, value):
print(value)
print(type(value))


if __name__ == '__main__':
unittest.main()

元祖或列表

1
2
3
4
5
6
7
8
9
10
11
12
13
import unittest
from ddt import ddt, data

@ddt()
class Test_ddt(unittest.TestCase):
@data((1, 'name'), (1, 'name'))
def test_01(self, value):
print(value)
print(type(value))


if __name__ == '__main__':
unittest.main()

“*形参”会把实参解析成元组

1
2
3
4
5
6
7
8
9
10
11
12
13
import unittest
from ddt import ddt, data

@ddt()
class Test_ddt(unittest.TestCase):
@data((1, 'name'), (1, 'name'))
def test_01(self, *value):
print(value)
print(type(value))


if __name__ == '__main__':
unittest.main()

“*实参”拆包元组或列表

1
2
3
4
5
6
7
8
9
10
11
12
13
import unittest
from ddt import ddt, data

@ddt()
class Test_ddt(unittest.TestCase):
@data(*(1, 'name'), *[2, 'age'])
def test_01(self, value):
print(value)
print(type(value))


if __name__ == '__main__':
unittest.main()

@unpack拆包元组或列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import unittest
from ddt import ddt, data, unpack

@ddt()
class Test_ddt(unittest.TestCase):
@data((1, 'name'), (2, 'name'))
@unpack
def test_01(self, value, name):
print(value)
print(name)
print(type(value))
print(type(name))


if __name__ == '__main__':
unittest.main()

@unpack和“*实参”拆包元组或列表

注意:

  • @unpack在实参传递时拆包
  • “*实参”在定义时拆包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import unittest
from ddt import ddt, data, unpack

@ddt()
class Test_ddt(unittest.TestCase):
@data(*[(1, 'name'), [2, 'age']])
@unpack
def test_01(self, value, age):
print(value)
print(age)


if __name__ == '__main__':
unittest.main()

字典

1
2
3
4
5
6
7
8
9
10
11
12
13
import unittest
from ddt import ddt, data

@ddt()
class Test_ddt(unittest.TestCase):
@data({'name': '小明', 'age': 19}, {'name': '小黑', 'age': 20})
def test_01(self, value):
print(value)
print(type(value))


if __name__ == '__main__':
unittest.main()

@unpack拆包字典

注意:

  • @unpack解包字典型实参后为关键字参数,形参名必须是值的键,也就是关键字
  • 如果参数名和键不一致,会报异常:TypeError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import unittest
from ddt import ddt, data, unpack

@ddt()
class Test_ddt(unittest.TestCase):
@data({'name': '小明', 'age': 19}, {'name': '小黑', 'age': 20})
@unpack
def test_01(self, name, age):
print(name)
print(age)
print(type(name))
print(type(age))


if __name__ == '__main__':
unittest.main()

“**形参”会把实参解析成字典

注意:

  • 实参必须是关键字参数,如@unpack解包字典型实参后为关键字参数,可以在用“**形参”解析成字典
  • 如果实参不符合关键字参数格式,会报异常:TypeError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import unittest
from ddt import ddt, data, unpack

@ddt()
class Test_ddt(unittest.TestCase):
@data({'name': '小明', 'age': 19}, {'name': '小黑', 'age': 20})
@unpack
def test_01(self, **value):
print(value)
print(type(value))


if __name__ == '__main__':
unittest.main()

总结:

@data(实参)的实参传递关键字参数给测试用例方法会报错,所以“**实参”拆包字典后不能作为@data()的参数

@file_data结合yaml使用

@file_data会自动拆包yaml文件的列表和字典数据

注意:

  • 对于yaml文件定义的列表会自动拆包
  • 对于yaml文件定义的字典会自动拆包,第一层拆包后为普通参数
  • 如果列表或字典的第二层是字典,第二层还会拆包,第二层拆包成关键字参数
  • 字典拆包成关键字参数,形参必须以关键字命名

函数的参数

函数的形参和实参

定义的参数称为:形参

调用实际传递的为:实参

参数传递的两种方式

位置参数:按位置顺序进行传递

关键字参数:通过关键字指定传给某个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 位置参数(按位置顺序进行传递)
def add_num(a, b): # a,b是形参,没有真正的值,用于接收实参
return a + b

res1 = add_num(67, 12) # 此时传入的67,12是实参
print(res1)

# 关键字参数(通过关键字指定传给某个参数)
res2 = add_num(b=34, a=12, c=11)
print(res2)

# 参数混用时,位置参数写最前面,关键字参数写在后面,不能重复传
res3 = add_num(30, c=12, b=12)
print(res3)

函数定义的三种形参

  • 必需参数 如:def add_num(a, b, c) ,a,b,c三个参数都必须要传
  • 默认参数(缺省参数) 如:def add_num(a, b, c=99) ,c是默认参数,可以传,可以不传(不传时直接使用c=99)
  • 不定长参数:如:def add_num(a, b, *args, **kwargs) ,其中a和b是必需参数,*args 可以不传,也可以传多个,它接收必需参数、默认参数完之外的位置参数,以元组的形式保存,**kwargs 可以不传,也可以传多个,接收必需参数、默认参数之外的关键字参数,以字典的形式保存
1
2
3
4
5
6
7
8
9
10
def test(a, b, c, *args, **kwargs):
print("a的值", a)
print("b的值", b)
print("c的值", c)
print("*args的值", args)
print("**kwargs的值", kwargs)

va1 = test(11, 22, 33, 4, 3, 2)
va2 = test(11, 5, 6, 7, g=33, k=22, f=12)
print(va1, va2)

扩展:不定长参数可以使用*对元组(列表也可以,但一般不用)进行拆包,**可以对字典进行拆包,拆包原理如下:

1
2
3
4
5
6
7
8
9
# 拆包
def func():
return 11,22,33 # 当返回结果有多个值时,会以元组的形式返回

c1 = func()
print(c1) # 返回的是一个元组(11,22,33)

a,b,c =func() # 可以通过变量把返回的元组进行拆包,接收对应的值,a接收了11,b接收了22,c接收了33
print(a,b,c) # 返回对应的元素11,22,33

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def func(a, b, c):
print('这是', a)
print('这是', b)
print('这是', c)

# 利用*、**分别对元组、字典拆包:只能用在函数调用时使用
# 对元组进行拆包
func(*(1, 2, 3))

print('-------分割线-------')
tu = (4, 5, 6)
func(*tu)

print('-------分割线-------')
# 对字典进行拆包, 注意**kwargs接收的是接收必需参数、默认参数之外的关键字参数
# 因此字典的键要与函数的形参一致
dic = {'a': 88, 'b': 90, 'c': 91}
func(**dic)

xlrd的使用

xlrd安装

pip install xlrd

xlrd使用

导入库:import xlrd

excel操作

excel读取:xlrd.open_wordbook(excel_path) 打开excel工作簿

sheet表单操作:

  • table.sheets()[0] 通过索引顺序获取
  • table.sheet_names() 获取所有表单名字
  • table.sheet_by_index(sheet_indx) 通过索引获取表单
  • table.sheet_by_name(sheet_name) 通过名称获取表单

单元格操作:

  • sheet.cell(i,j) 返回单元格对象注:i表示行数,j表示列数(i<=nrows,j<=ncols)
  • sheet.cell_type(i,j) 返回单元格中的数据类型
  • sheet.cell_value(i,j) 返回单元格中的数据

行操作:

  • sheet.nrows 获取该表单中的有效行数
  • sheet.row_values(i, start_colx=0, end_colx=None) 返回由该行中所有单元格的数据类型组成的列表
  • sheet.row_types(i, start_colx=0, end_colx=None) 返回由该行中所有单元格的数据组成的列表

列操作:

  • sheet.ncols 获取列表的有效列数,注:i表示第几列,不能超过总列数(i<=ncols)
  • sheet.col_values(i, start_rowx=0, end_rowx=None) 返回由该列中所有单元格的数据组成的列表
  • sheet.col_types(i, start_rowx=0, end_rowx=None) 返回由该列中所有单元格的数据类型组成的列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import xlrd

# 打开工作簿xls文件
table = xlrd.open_workbook("../data/test_data.xls")
# 获取具体的一张表(sheet)
sheet_index = table.sheet_by_index(0) # 通过索引获取
sheet_name = table.sheet_by_name("Sheet1") # 通过表单名获取
# 读取表中的数据
row_values = sheet_index.row_values(0) # 通过索引获取行
print(row_values)
col_values = sheet_name.col_values(0) # 通过索引获取列
print(col_values)
cell_value = sheet_index.cell_value(0, 0) # 获取具体单元格内容
print(cell_value)
# 获取表中列数和行数
rows = sheet_index.nrows # 获取总行数
print(rows)
cols = sheet_name.ncols # 获取总列数
print(cols)
# 判断单元格数据类型
"""
1:str
2:int
3:date
4:boolean
"""
cell_type = sheet_index.cell_type(1,1)
print(cell_type)

excel操作实战

test_data.xls:

operationexcel.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import xlrd

# 创建操作excel表格的类
class OperationExcel:
# 初始化类 filename:路径+文件名
def __init__(self, filename):
self.table = xlrd.open_workbook(filename)

# 通过表单索引读取数据
def get_data_by_index(self, index=0):
sheet = self.table.sheet_by_index(index) # 获取表单
return self._get_data_info(sheet)

# 通过表单名字读取
def get_data_by_name(self, name):
sheet = self.table.sheet_by_name(name)
return self._get_data_info(sheet)

# 获取数据详情
def _get_data_info(self, sheet):
keys = sheet.row_values(0) # 将表格的第一行作为字典的key
rows = sheet.nrows # 获取所有行数
cols = sheet.ncols # 获取所有列数
data_list = []
for row in range(1, rows):
values = []
for col in range(cols):
value = self.read_cell(sheet, row, col)
values.append(value)
# zip():函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表
# a = [1,2,3] b = [4,5,6] zip(a, b) --> [(1, 4), (2, 5), (3, 6)]
ziped = zip(keys, values)
data_list.append(dict(ziped)) # 将列表转化成字典追加到列表中
return data_list

# 对单元格内容进行类型转换
def read_cell(self, sheet, row, col):
sheet = self.table.sheet_by_index(0)
cell_value = sheet.cell_value(row, col)
cell_type = sheet.cell_type(row, col)
if cell_type == 1: # 当单元格数据类型为str
cell_value = cell_value
elif cell_type == 2 and cell_value % 1 == 0: # 当单元格内容为int
cell_value = int(cell_value)
elif cell_type == 4: # 当单元格数据类型为布尔值
cell_value = True if cell_value == 1 else False # 三目运算符
return cell_value


if __name__ == '__main__':
op_ex = OperationExcel("../data/test_data.xls")
# data_list = op_ex.get_data_by_index()
data_list = op_ex.get_data_by_name("Sheet1")
print(data_list)

读取txt文件

1
2
3
4
5
6
7
file=open("../data/test_data.txt", 'r', encoding='utf-8')
userlines = file.readlines()
file.close()
for line in userlines:
username=line.split(',')[0] # 读取用户名
password=line.split(',')[1] # 读取密码
print(username, password)

读取csv文件

1
2
3
4
5
6
7
8
9
import csv

filepath = "../data/login_data.csv"
filename = open(filepath, "r")
reader = csv.reader(filename)
for row in reader:
# 数组下标是以0开始的
print("username:%s"%row[0],"password:%s"%row[1])
filename.close()