YAML 配置文件
在接口自动化测试中,管理大量的接口信息和测试用例是一项复杂的任务。为了提高可读性和可维护性,使用 YAML 文件来管理这些信息是一个不错的选择。
忽略 HTTPS 证书验证
Section titled “忽略 HTTPS 证书验证”在开始之前,先拓展一个小知识点。当我们使用 requests 模块发送 HTTPS 请求时,如果目标网站的 SSL 证书不被信任,可能会导致请求失败。为了解决这个问题,可以在请求中添加 verify=False 参数,忽略 SSL 证书验证。
import requests
response = requests.get('https://example.com', verify=False)注意: 在生产环境中,忽略 SSL 证书验证可能会带来安全风险,建议仅在测试环境中使用。
了解 YAML 文件
Section titled “了解 YAML 文件”什么是 YAML?
Section titled “什么是 YAML?”YAML(YAML Ain’t Markup Language)是一种专门用来写配置文件的语言,具有简洁、易读的特点。相比 JSON,YAML 更加人性化,适合用来编写配置文件和数据交换。
YAML 的基本语法
Section titled “YAML 的基本语法”-
大小写敏感
-
使用缩进表示层级关系
-
不允许使用制表符缩进,只能使用空格
key: value2)嵌套结构
Section titled “2)嵌套结构”parent: child1: value1 child2: value2items: - item1 - item2 - item3注意: 冒号后面必须有一个空格,否则会导致语法错误。
使用 # 进行单行注释:
# 这是一个注释key: value # 行尾注释编写接口测试用例的 YAML 文件
Section titled “编写接口测试用例的 YAML 文件”下面以用户登录接口为例,演示如何将接口信息和测试用例写入 YAML 文件。
定义基本结构
Section titled “定义基本结构”创建一个名为 login.yaml 的文件,内容如下:
# 接口基本信息base_info: api_name: 用户登录 url: /login method: post headers: Content-Type: application/x-www-form-urlencoded编写测试用例
Section titled “编写测试用例”# 测试用例test_cases: - case_name: 正确的用户名和密码 data: username: test_user password: test_pass validations: - contains: 登录成功 extract: - token
- case_name: 错误的用户名 data: username: wrong_user password: test_pass validations: - contains: 用户名或密码错误完整的 YAML 文件
Section titled “完整的 YAML 文件”base_info: api_name: 用户登录 url: /login method: post headers: Content-Type: application/x-www-form-urlencoded
test_cases: - case_name: 正确的用户名和密码 data: username: test_user password: test_pass validations: - contains: 登录成功 extract: - token
- case_name: 错误的用户名 data: username: wrong_user password: test_pass validations: - contains: 用户名或密码错误在 Python 中读取 YAML 文件
Section titled “在 Python 中读取 YAML 文件”安装 PyYAML 库
Section titled “安装 PyYAML 库”在读取 YAML 文件之前,需要安装 PyYAML 库:
pip install pyyaml编写读取函数
Section titled “编写读取函数”import yaml
def read_yaml(file_path): """ 读取 YAML 文件并返回数据 :param file_path: YAML 文件路径 :return: 文件内容 """ try: with open(file_path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) except Exception as e: print(f"读取 YAML 文件出错: {e}") return None测试读取函数
Section titled “测试读取函数”data = read_yaml('login.yaml')print(data)使用读取的数据发送请求
Section titled “使用读取的数据发送请求”封装请求方法
Section titled “封装请求方法”import requests
class APIRequest: def __init__(self): pass
def send_request(self, method, url, data=None, headers=None): try: response = requests.request( method=method, url=url, data=data, headers=headers, verify=False # 忽略 SSL 证书验证 ) return response except Exception as e: print(f"请求出错: {e}") return None组装请求数据并发送请求
Section titled “组装请求数据并发送请求”def main(): base_url = 'https://example.com' # 基础 URL yaml_data = read_yaml('login.yaml')
if not yaml_data: return
base_info = yaml_data.get('base_info', {}) test_cases = yaml_data.get('test_cases', [])
api_name = base_info.get('api_name') url = base_url + base_info.get('url', '') method = base_info.get('method', 'get') headers = base_info.get('headers', {})
api_request = APIRequest()
for case in test_cases: case_name = case.get('case_name') data = case.get('data', {}) validations = case.get('validations', []) extract = case.get('extract', [])
print(f"执行用例: {case_name}")
response = api_request.send_request( method=method, url=url, data=data, headers=headers )
if response: response_text = response.text # 进行断言验证 for validation in validations: if 'contains' in validation: expected_text = validation['contains'] assert expected_text in response_text, f"期望包含: {expected_text}" # 进行数据提取 # ...(根据需要实现) print(f"用例 '{case_name}' 执行成功!") else: print(f"用例 '{case_name}' 执行失败!")
if __name__ == '__main__': main()处理编码问题
Section titled “处理编码问题”在读取文件时,可能会遇到编码问题,常见的是 utf-8 和 gbk 编码不一致导致的错误。为确保读取正确,可以在打开文件时指定编码:
with open(file_path, 'r', encoding='utf-8') as f: # 读取文件内容如果文件实际编码为 gbk,则需要将 encoding 参数修改为 gbk。
建议: 在创建 YAML 文件时,统一使用 utf-8 编码,避免编码不一致的问题。
在 Python 中将接口返回值写入指定的 YAML 文件
Section titled “在 Python 中将接口返回值写入指定的 YAML 文件”在自动化测试和接口测试中,我们经常需要将接口的返回值提取并保存,以供后续测试使用。本节将详细讲解如何使用 Python 将接口的返回值写入到指定的 YAML 文件中。同时,我们还会介绍 JSON 的序列化和反序列化,以帮助更好地处理数据。
定义读取和写入 YAML 数据的工具类
Section titled “定义读取和写入 YAML 数据的工具类”首先,我们需要定义一个工具类,用于读取和写入 YAML 文件。我们可以将读取和写入的方法封装到一个类中,便于重复使用。
import yamlimport os
class YAMLUtil: """读取和写入 YAML 数据的工具类"""
def __init__(self, file_path=None): """ 初始化方法,接受一个文件路径。如果未提供路径,使用默认的 'extract.yaml'。 """ if file_path is not None: self.file_path = file_path else: self.file_path = 'extract.yaml' # 默认的 YAML 文件在 init 方法中,我们允许用户在实例化类时传入一个文件路径。如果没有传入,则使用默认的 extract.yaml 文件。
将数据写入到 YAML 文件
Section titled “将数据写入到 YAML 文件”接下来,我们需要编写一个方法,将指定的数据写入到 YAML 文件中。
class YAMLUtil: # ... 前面的代码省略
def write_yaml(self, data): """ 将数据写入到 YAML 文件中。
:param data: 要写入的数据,必须是字典类型。 """ if not isinstance(data, dict): raise ValueError("写入的数据必须为字典类型")
# 确保目录存在 dir_name = os.path.dirname(self.file_path) if dir_name and not os.path.exists(dir_name): os.makedirs(dir_name)
# 写入数据到 YAML 文件 with open(self.file_path, 'a', encoding='utf-8') as f: yaml.dump(data, f, allow_unicode=True)-
类型检查:首先,我们检查传入的数据是否为字典类型,如果不是,抛出 ValueError 异常。
-
确保目录存在:在写入文件之前,我们需要确保目录存在。如果目录不存在,使用 os.makedirs 创建目录。
-
写入数据:使用 with open 语句打开文件,模式为 ‘a’,表示追加写入。使用 yaml.dump 方法将数据写入文件,allow_unicode=True 确保可以正确写入中文字符。
示例:将接口返回的 Token 写入到 YAML 文件
Section titled “示例:将接口返回的 Token 写入到 YAML 文件”假设我们有一个登录接口,返回了一个包含 Token 的响应。我们希望将这个 Token 写入到 YAML 文件中。
import requests
# 模拟接口响应response = requests.Response()response._content = b'{"token": "abc123", "expires_in": 3600}'response.status_code = 200
# 从响应中提取 Tokentoken = response.json().get('token')
# 实例化 YAML 工具类yaml_util = YAMLUtil()
# 准备要写入的数据data_to_write = {'token': token}
# 写入数据到 YAML 文件yaml_util.write_yaml(data_to_write)-
模拟接口响应:这里我们模拟了一个接口响应,实际使用中可以直接使用 requests 库的响应对象。
-
提取 Token:使用 response.json().get(‘token’) 提取返回值中的 Token。
-
实例化工具类:创建 YAMLUtil 的实例。
-
准备数据:将 Token 放入字典中,键名为 ‘token’。
-
写入数据:调用 write_yaml 方法,将数据写入到 YAML 文件中。
文件写入模式:‘w’ vs ‘a’
Section titled “文件写入模式:‘w’ vs ‘a’”在文件操作中,写入模式非常重要,主要有以下两种模式:
-
‘w’ 模式(写入模式):每次写入都会覆盖文件原有的内容。
-
‘a’ 模式(追加模式):每次写入都会在文件末尾追加内容。
在上面的示例中,我们使用了 ‘a’ 模式,以确保每次写入的数据不会覆盖之前的数据。如果希望每次都覆盖文件,可以将模式改为 ‘w’。
with open(self.file_path, 'w', encoding='utf-8') as f: yaml.dump(data, f, allow_unicode=True)扩展:JSON 的序列化和反序列化
Section titled “扩展:JSON 的序列化和反序列化”在接口测试中,我们经常需要处理 JSON 数据。因此,理解 JSON 的序列化和反序列化非常重要。
1. 什么是 JSON 序列化和反序列化
Section titled “1. 什么是 JSON 序列化和反序列化”-
序列化:将 Python 对象(如字典)转换为 JSON 格式的字符串。
-
反序列化:将 JSON 格式的字符串转换为 Python 对象。
2. JSON 序列化示例
Section titled “2. JSON 序列化示例”import json
# Python 字典data = {'message': '成功', 'code': 200}
# 序列化为 JSON 字符串json_str = json.dumps(data, ensure_ascii=False)print(json_str) # 输出:{"message": "成功", "code": 200}3. 处理 Unicode 字符
Section titled “3. 处理 Unicode 字符”默认情况下,json.dumps 会将非 ASCII 字符转换为 Unicode 编码。为了正确显示中文,需要设置 ensure_ascii=False。
json_str = json.dumps(data, ensure_ascii=False)4. JSON 反序列化示例
Section titled “4. JSON 反序列化示例”# JSON 字符串json_str = '{"message": "成功", "code": 200}'
# 反序列化为 Python 字典data = json.loads(json_str)print(data) # 输出:{'message': '成功', 'code': 200}在 YAML 测试用例中实现参数的动态替换
Section titled “在 YAML 测试用例中实现参数的动态替换”在 API 测试中,将参数硬编码到测试用例中既不实用也不具备可扩展性。像令牌、加密密码或动态生成的值等参数需要以一种在运行时无需修改测试脚本就能更新或替换的方式进行处理。这就是参数动态替换或“热加载”的作用所在。
在本文中,我们将探讨如何使用 Python 在 YAML 测试用例中实现参数的动态替换。我们将涵盖创建一个 debugtalk 模块来存放我们的动态函数、读取如令牌等提取的数据、实现 MD5 加密、解析 YAML 文件中的占位符,以及使用反射动态调用函数。
硬编码参数的问题
Section titled “硬编码参数的问题”在创建 API 测试用例时,您可能会倾向于将参数直接硬编码到 YAML 文件中:
request: method: POST url: /api/login headers: Content-Type: application/json json: username: "user1" password: "123456" # 硬编码的密码但是,这种方法存在几个问题:
-
动态数据:某些参数需要在运行时生成或转换,例如加密密码或时间戳。
-
可维护性:硬编码的值使得在多个测试用例中更新或管理参数变得困难。
-
可重用性:您无法在不修改文件的情况下使用不同的数据重用测试用例。
为了解决这些问题,我们需要一种在测试用例中动态替换参数的方法。
引入 debugtalk 模块
Section titled “引入 debugtalk 模块”debugtalk 模块是我们框架中的核心组件,它包含了所有需要的动态函数和工具,用于参数替换。通过在此模块中定义函数,我们可以直接在 YAML 文件中调用它们。
创建 debugtalk.py 模块
Section titled “创建 debugtalk.py 模块”首先,在您的项目目录中创建一个名为 debugtalk.py 的文件:
touch debugtalk.py在 debugtalk.py 中,您可以定义执行诸如读取数据、加密密码或生成令牌等任务的函数。
动态读取提取的数据
Section titled “动态读取提取的数据”假设我们有一个令牌需要包含在请求的头部,但这个令牌是在运行时生成的,并在先前的 API 调用后存储在一个提取文件中。
提取文件 (extract.yaml)
Section titled “提取文件 (extract.yaml)”token: "REDACTED"product_ids: - "123456" - "234567" - "345678"我们需要在发送请求时动态地读取这个令牌。
从 extract.yaml 中读取数据
Section titled “从 extract.yaml 中读取数据”在 debugtalk.py 中,添加一个函数来从提取文件中读取数据:
import osimport yaml
def read_extract(key): """根据提供的键从 extract.yaml 中读取值。""" extract_path = os.path.join(os.getcwd(), 'extract.yaml') if not os.path.exists(extract_path): print("提取文件不存在。") return None with open(extract_path, 'r', encoding='utf-8') as f: data = yaml.safe_load(f) return data.get(key)实现 MD5 加密函数
Section titled “实现 MD5 加密函数”我们可能需要在发送请求前使用 MD5 加密密码。
MD5 加密函数
Section titled “MD5 加密函数”在 debugtalk.py 中添加以下函数:
import hashlib
def md5_encrypt(password): """使用 MD5 加密密码。""" md5 = hashlib.md5() md5.update(password.encode('utf-8')) return md5.hexdigest()解析并替换 YAML 文件中的占位符
Section titled “解析并替换 YAML 文件中的占位符”我们可以在 YAML 文件中使用占位符来指示需要动态替换的值。
在 YAML 中使用占位符
Section titled “在 YAML 中使用占位符”request: method: POST url: /api/login headers: Content-Type: application/json Authorization: "Bearer ${read_extract(token)}" json: username: "user1" password: "${md5_encrypt(123456)}"解析并替换占位符
Section titled “解析并替换占位符”在新的模块 api_util.py 中创建一个实用函数来解析和替换这些占位符:
import refrom debugtalk import read_extract, md5_encrypt
def replace_placeholder(data): """用实际值替换数据中的占位符。""" pattern = re.compile(r"\$\{(\w+)(\((.*?)\))?\}") if isinstance(data, dict): data_str = str(data) else: data_str = data
matches = pattern.findall(data_str) for func_name, _, args in matches: func = globals().get(func_name) if func: if args: args_list = args.split(',') value = func(*args_list) else: value = func() placeholder = f"${{{func_name}({args})}}" data_str = data_str.replace(placeholder, value) return eval(data_str)该函数使用正则表达式查找占位符,并通过调用 debugtalk.py 中的相应函数来替换它们。
使用反射动态调用函数
Section titled “使用反射动态调用函数”Python 的反射功能允许我们在运行时按名称调用函数,这对于我们的参数动态替换至关重要。
使用 getattr 进行反射
Section titled “使用 getattr 进行反射”在 api_util.py 中,修改 replace_placeholder 函数以使用 getattr:
def replace_placeholder(data): """用实际值替换数据中的占位符。""" pattern = re.compile(r"\$\{(\w+)(\((.*?)\))?\}") if isinstance(data, dict): data_str = str(data) else: data_str = data
matches = pattern.findall(data_str) for func_name, _, args in matches: func = getattr(debugtalk, func_name, None) if func: if args: args_list = [arg.strip() for arg in args.split(',')] value = func(*args_list) else: value = func() placeholder = f"${{{func_name}({args})}}" data_str = data_str.replace(placeholder, f'"{value}"') return eval(data_str)这里,getattr 用于按名称从 debugtalk.py 中获取函数。
处理不同的数据类型和场景
Section titled “处理不同的数据类型和场景”在某些情况下,我们提取的数据可能是列表或字典,需要适当地处理它们。
读取列表元素
Section titled “读取列表元素”假设我们在 extract.yaml 中有一个产品 ID 的列表:
product_ids: - "123456" - "234567" - "345678"我们可以修改 read_extract 以处理索引:
def read_extract(key, index=None): """根据提供的键和索引从 extract.yaml 中读取值。""" extract_path = os.path.join(os.getcwd(), 'extract.yaml') if not os.path.exists(extract_path): print("提取文件不存在。") return None with open(extract_path, 'r', encoding='utf-8') as f: data = yaml.safe_load(f) value = data.get(key) if isinstance(value, list) and index is not None: return value[int(index)] return value在 YAML 文件中:
url: "/api/products/${read_extract(product_ids, 0)}"现在,read_extract 可以处理索引以检索特定的产品 ID。
在 YAML 测试用例中实现参数的动态替换,可以显著提高 API 测试的灵活性和可维护性。通过利用 Python 的反射功能和解析技术,我们可以创建一个强大的测试框架,能够处理动态数据和复杂的场景。
优势:
-
动态数据处理:轻松管理令牌、加密密码和其他动态参数。
-
可维护性:在 debugtalk.py 中集中动态函数,便于更新。
-
可重用性:编写一次测试用例,使用不同的数据重复使用。
下一步:
-
扩展功能:根据需要添加更多的实用函数。
-
错误处理:为缺少键或无效数据实现健壮的错误处理。
-
测试:为您的实用函数编写单元测试以确保可靠性。