八分钟学会YAML数据驱动

转载请注明出处❤️

作者:测试蔡坨坨

原文链接:caituotuo.top/90d52d38.html


你好,我是测试蔡坨坨。

众所周知,我们在做自动化测试的时候,通常会把配置信息和测试数据存储到特定的文件中,以实现数据和脚本的分离,从而提高代码的易读性和可维护性,便于后期优化。而配置文件的形式更是多种多样,比如:ini、yaml、json、toml、py、xml、properties等。

在之前介绍的接口和Web UI自动化测试框架 ——「五分钟学会接口自动化测试框架」和「五分钟搞懂 POM 设计模式」中都是通过yaml文件进行配置信息和测试数据的管理,所以今天我们就来聊一聊YAML数据驱动。

1 什么是YAML

YAML:YAML Ain’t a Markup Language,翻译过来就是「YAML不是一种标记语言」。

但是在开发这种语言时,YAML的意思其实是Yet Another Markup Language「仍是一种标记语言」。

它是一种以数据为中心的标记语言,比 XML 和 JSON 更适合作为配置文件。

YAML的配置文件后缀为.yml.yaml,如:caituotuo.ymlcaituotuo.yaml

YAML的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲等。

2 YAML语法

2.1 基本语法

  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格(官方说法不允许使用tab,当然如果你使用tab在某些地方也是可以的,例如在PyCharm软件上)
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • 大小写敏感
  • 前面加上#表示注释

例如:

1
2
3
4
5
6
7
8
req:
username: 测试蔡坨坨 # 这是姓名
gender: Boy
ip: 上海
blog: www.caituotuo.top
res:
status: 1
code: 200

2.2 数据类型

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

纯量是指单个的,不可拆分的值,例如:数字、字符串、布尔值、Null、日期等,纯量直接写在键值对的value中即可。

字符串:

默认情况下字符串是不需要使用单引号或双引号的

1
username: 测试蔡坨坨

当然使用双引号或者单引号包裹字符也是可以的

1
2
username: 'Hello world 蔡坨坨'
username: "Hello world 蔡坨坨"

字符串可以拆成多行,每一行会被转化成一个空格

1
2
3
# 字符串可以拆成多行,每一行会被转化成一个空格 '测试 蔡坨坨'
username3: 测试
蔡坨坨
布尔值:
1
2
3
4
5
boolean:
- TRUE #true,True都可以
- FALSE #false,False都可以

# {'boolean': [True, False]}
数字:
1
2
3
4
5
6
7
8
float:
- 3.14
- 6.8523015e+5 #可以使用科学计数法
int:
- 123
- 0b1010_0111_0100_1010_1110 #二进制表示

# {'float': [3.14, 685230.15], 'int': [123, 685230]}
Null:
1
2
3
4
5
6
7
null:
nodeName: 'node'
parent: ~ #使用~表示null
parent2: None #使用None表示null
parent3: null #使用null表示null

# {None: {'nodeName': 'node', 'parent': None, 'parent2': 'None', 'parent3': None}}
时间和日期:
1
2
3
4
5
6
date:
- 2018-02-17 #日期必须使用ISO 8601格式,即yyyy-MM-dd
datetime:
- 2018-02-17T15:02:31+08:00 #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区

# {'date': [datetime.date(2018, 2, 17)], 'datetime': [datetime.datetime(2018, 2, 17, 15, 2, 31, tzinfo=datetime.timezone(datetime.timedelta(seconds=28800)))]}
对象

使用key:[空格]value的形式表示一对键值对(空格不能省略),例如:blog: caituotuo.top

行内写法:

1
key: {key1: value1, key2: value2, ...}

普通写法,使用缩进表示对象与属性的层级关系:

1
2
3
key: 
child-key: value
child-key2: value2
数组

- 开头的行表示构成一个数组。

普通写法:

1
2
3
4
name:
- 测试蔡坨坨
- 小趴蔡
- 蔡蔡

YAML 支持多维数组,可以使用行内表示:

1
key: [value1, value2, ...]

数据结构的子成员是一个数组,则可以在该项下面缩进一个空格:

1
2
3
4
5
6
7
8
9
10
11
username:
-
- 测试蔡坨坨
- 小趴蔡
- 蔡蔡
-
- A
- B
- C

# {'username': [['测试蔡坨坨', '小趴蔡', '蔡蔡'], ['A', 'B', 'C']]}

相对复杂的例子:

companies 属性是一个数组,每一个数组元素又是由 id、name、price 三个属性构成

1
2
3
4
5
6
7
8
9
10
11
companies:
-
id: 1
name: caituotuo
price: 300W
-
id: 2
name: 测试蔡坨坨
price: 500W

# {'companies': [{'id': 1, 'name': 'caituotuo', 'price': '300W'}, {'id': 2, 'name': '测试蔡坨坨', 'price': '500W'}]}

数组也可以使用flow流式的方式表示:

1
companies2: [ { id: 1,name: caituotuo,price: 300W },{ id: 2,name: 测试蔡坨坨,price: 500W } ]
复合结构

以上三种数据结构可以任意组合使用,以实现不同的用户需求,例如:

1
2
3
4
5
6
7
8
9
10
platform:
- 公众号
- 小红书
- 博客
sites:
公众号: 测试蔡坨坨
小红书: 测试蔡坨坨
blog: caituotuo.top

# {'platform': ['公众号', '小红书', '博客'], 'sites': {'公众号': '测试蔡坨坨', '小红书': '测试蔡坨坨', 'blog': 'caituotuo.top'}

3 引用

& 锚点和 * 别名,可以用来引用。

举个栗子:

& 用来建立锚点defaults,<< 表示合并到当前数据,* 用来引用锚点

1
2
3
4
5
6
7
8
9
10
11
defaults: &defaults
adapter: postgres
host: localhost

development:
database: myapp_development
<<: *defaults

test:
database: myapp_test
<<: *defaults

等价于:

1
2
3
4
5
6
7
8
9
10
11
12
13
defaults:
adapter: postgres
host: localhost

development:
database: myapp_development
adapter: postgres
host: localhost

test:
database: myapp_test
adapter: postgres
host: localhost

4 组织结构

一个YAML文件可以由一个或多个文档组成,文档之间使用---作为分隔符,且整个文档相互独立,互不干扰,如果YAML文件只包含一个文档,则---分隔符可以省略。

1
2
3
4
5
6
7
8
9
10
---
website:
name: 测试蔡坨坨
url: caituotuo.top
---
website: { name: 测试蔡坨坨,url: www.caituotuo.top }
---
公众号: 测试蔡坨坨
---
小红书: 测试蔡坨坨
1
2
3
4
5
f7 = "./files/多文档.yml"
with open(f7, "r", encoding="UTF-8") as f:
content = yaml.safe_load_all(f)
for i in content:
print(i)
1
2
3
4
5
6
运行结果:

{'website': {'name': '测试蔡坨坨', 'url': 'caituotuo.top'}}
{'website': {'name': '测试蔡坨坨', 'url': 'www.caituotuo.top'}}
{'公众号': '测试蔡坨坨'}
{'小红书': '测试蔡坨坨'}

5 实战

封装思路

将YAML相关操作封装成CommonUtil公共模块,之后直接引入调用即可。

相关功能:

  1. 读取yaml文件数据
  2. 将yaml数据转换成json格式
  3. 可以动态设置参数

这里要说一下动态设置参数

在自动化测试中,肯定不能把所有的参数都写死,因此就会用到参数化,例如:提取前一个接口的返回值作为后一个接口的入参,这里通过Python中的Template模块进行动态参数的设置

yaml文件中通过$变量名的形式设置变量

1
username: $username

给变量附上具体的值

1
2
3
4
with open(yaml_path, "r", encoding="UTF-8") as f:
text = f.read()
# Template(text).safe_substitute(key_value)
Template(text).safe_substitute({"username": "测试蔡坨坨"}) # username为变量名

完整代码

源码获取请关注公众号:测试蔡坨坨,回复关键词:源码

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# author: 测试蔡坨坨
# datetime: 2022/9/4 18:04
# function: Python操作YAML文件

import os
from string import Template

import yaml


class YamlUtil:
@staticmethod
def yaml_util(yaml_path, key_value=None):
"""
读取yml文件 设置动态变量
:param yaml_path: 文件路径
:param key_value: 动态变量 如:{"username": "测试蔡坨坨"} yaml中的变量:$username
:return:
"""
try:
with open(yaml_path, "r", encoding="UTF-8") as f:
text = f.read()
if key_value is not None:
re = Template(text).safe_substitute(key_value)
json_data = yaml.safe_load(re)
else:
json_data = yaml.safe_load(text)
return json_data
except FileNotFoundError:
raise FileNotFoundError("文件不存在")
except Exception:
raise Exception("未知异常")

@staticmethod
def multiple(yaml_path):
"""
多文档
:param yaml_path: yaml文件路径
:return: list
"""
json_data = []
try:
with open(yaml_path, "r", encoding="UTF-8") as f:
content = yaml.safe_load_all(f)
for i in content:
json_data.append(i)
return json_data
except FileNotFoundError:
raise FileNotFoundError("文件不存在")
except Exception:
raise Exception("未知异常")


if __name__ == '__main__':
f1 = "./files/初体验.yml"
print(YamlUtil().yaml_util(f1))

f2 = "./files/纯量.yml"
print(YamlUtil().yaml_util(f2))

f3 = "./files/数组.yml"
print(YamlUtil().yaml_util(f3))

f4 = "./files/复合结构.yml"
print(YamlUtil().yaml_util(f4))

f5 = "./files/引用.yml"
print(YamlUtil().yaml_util(f5))

f6 = "./files/参数化.yml"
print(YamlUtil().yaml_util(f6, {"username": "测试蔡坨坨"}))

f7 = "./files/多文档.yml"
for i in YamlUtil().multiple(f7):
print(i)