pytest与YAML结合:构建数据驱动与配置解耦的自动化测试框架

📅 2026/6/18 4:34:08
pytest与YAML结合:构建数据驱动与配置解耦的自动化测试框架
1. 项目概述为什么是 pytest YAML如果你正在做自动化测试尤其是接口自动化那你大概率听说过或者正在用 pytest。它灵活、强大、社区活跃几乎是 Python 测试领域的“事实标准”。但当我们把测试用例数量堆到成百上千时一个头疼的问题就来了测试数据和管理。代码里硬编码测试数据改个参数就得翻代码维护成本爆炸。用 Excel 或 CSV处理复杂嵌套结构、列表数据时格式又显得笨重且不直观。这时候YAML 就登场了。它用缩进表达层级支持列表、字典、甚至锚点引用写配置和测试数据就像写一份结构清晰的清单人类和机器都爱读。所以“pytest YAML 完整实战指南”这个标题瞄准的就是这个痛点如何用 YAML 优雅地管理 pytest 的测试数据与配置实现测试脚本与数据的彻底分离从而构建可维护、易扩展的自动化测试框架。这不是简单的两个工具叠加而是一套提升测试工程化水平的组合拳。无论你是测试开发新手还是想优化现有测试框架的老手这套方案都能让你从“脚本小子”进阶到“框架设计师”。2. 核心设计思路数据驱动与配置解耦在深入代码之前我们先厘清核心设计思想。pytest 和 YAML 的结合主要服务于两个核心目的数据驱动测试和灵活配置管理。2.1 数据驱动测试让用例“活”起来数据驱动的核心是“一组脚本多组数据”。我们不再为每套数据写一个测试函数而是写一个通用的测试逻辑然后通过外部数据源这里是 YAML 文件来注入不同的测试数据。pytest 通过pytest.mark.parametrize装饰器完美支持这一点。为什么选择 YAML 作为数据源可读性极佳相比 JSON括号嵌套和 Excel无标准复杂结构YAML 的缩进格式非常接近自然书写习惯非技术人员也能轻松看懂测试用例。支持复杂结构可以轻松表示列表、字典的嵌套非常适合描述接口请求的headers、params、json等复杂数据。便于版本管理作为纯文本文件YAML 可以很好地被 Git 等版本控制系统管理方便追踪测试数据的变更历史。设计模式通常我们会为每个测试模块或功能域创建一个对应的 YAML 数据文件。例如test_user_api.py对应user_api_data.yaml。在 YAML 文件中我们用列表来组织一组组相关的测试数据。2.2 配置解耦环境、参数一键切换除了测试数据项目配置如不同环境的域名、数据库连接、全局请求头、超时时间等也应该从代码中剥离。YAML 同样是存放配置的绝佳选择。典型的多环境配置方案config.yaml存放所有环境的公共配置。config_dev.yaml开发环境特有配置继承或覆盖公共配置。config_test.yaml测试环境特有配置。config_prod.yaml生产环境配置通常用于冒烟或监控脚本。通过环境变量如ENVtest来控制加载哪个配置文件实现测试套件在不同环境间的无缝切换。2.3 架构蓝图一个典型的 pytest YAML 项目目录结构会是这样project/ ├── conftest.py # pytest 核心配置、Fixture定义 ├── pytest.ini # pytest 项目配置文件 ├── config/ # 配置文件目录 │ ├── config.yaml # 基础配置 │ ├── config_dev.yaml │ └── config_test.yaml ├── test_data/ # 测试数据目录 │ ├── user_data.yaml # 用户相关测试数据 │ └── order_data.yaml # 订单相关测试数据 ├── common/ # 公共模块 │ ├── __init__.py │ ├── read_data.py # YAML 读取工具类 │ └── request_util.py # 请求封装 └── testcases/ # 测试用例目录 ├── __init__.py ├── test_user_api.py └── test_order_api.py这个结构清晰地将配置、数据、工具和用例分离是构建可维护测试框架的基础。3. 环境搭建与核心工具链工欲善其事必先利其器。我们先来把必要的环境和工具准备好。3.1 基础环境安装首先确保你已安装 Python建议 3.7。然后通过 pip 安装核心库pip install pytest pyyaml pytest-html requestspytest测试框架本体。pyyaml用于读写 YAML 文件的 Python 库这是连接 pytest 和 YAML 的桥梁。pytest-html用于生成美观的 HTML 测试报告是提升测试结果可读性的利器。requests虽然与核心组合无直接关系但绝大多数接口自动化测试都会用到这里一并安装。注意建议使用虚拟环境如venv或conda来管理项目依赖避免包冲突。你可以通过python -m venv venv创建然后source venv/bin/activateLinux/Mac或venv\Scripts\activateWindows激活。3.2 YAML 语法快速入门为了有效编写 YAML 文件需要掌握其基本语法。它与 Python 的字典和列表有天然的映射关系。基本规则使用缩进表示层级关系不允许使用 Tab 键只允许使用空格通常为 2 或 4 个空格。键值对用冒号加空格表示key: value。列表数组用短横线加空格表示- item。#表示注释。看一个测试数据示例(test_data/user_login.yaml)# 用户登录接口测试数据 test_login: success: description: 正常登录-用户名密码正确 request: url: /api/v1/login method: POST json: username: test_user password: 123456 validate: - eq: [status_code, 200] - eq: [$.code, 0] # 使用JsonPath提取响应中的code字段 - contains: [$.message, 成功] fail_wrong_password: description: 异常登录-密码错误 request: url: /api/v1/login method: POST json: username: test_user password: wrong validate: - eq: [status_code, 401] - eq: [$.code, 1001]在这个例子里test_login是一个字典下面有success和fail_wrong_password两个键每个键对应的值又是一个字典包含了用例描述、请求信息和断言规则。这种结构一目了然。3.3 编写 YAML 读取工具我们需要一个可靠的工具来加载这些 YAML 文件。在common目录下创建read_data.pyimport yaml import os from pathlib import Path class YamlReader: YAML 文件读取工具类 def __init__(self, yaml_file): 初始化检查文件是否存在 :param yaml_file: YAML 文件路径 if os.path.exists(yaml_file): self.yaml_file yaml_file else: raise FileNotFoundError(fYAML文件不存在: {yaml_file}) def read(self): 读取整个 YAML 文件返回 Python 对象字典/列表 with open(self.yaml_file, r, encodingutf-8) as f: data yaml.safe_load(f) # 使用 safe_load 避免安全风险 return data or {} # 如果文件为空返回空字典 def get(self, key_path, defaultNone): 根据点分路径获取 YAML 中的嵌套值 例如: get(test_login.success.request.url) :param key_path: 点分路径字符串 :param default: 如果路径不存在返回的默认值 :return: 对应的值 data self.read() keys key_path.split(.) value data try: for key in keys: if isinstance(value, list): # 如果当前值是列表尝试将key转为索引 key int(key) value value[key] return value except (KeyError, IndexError, TypeError, ValueError): # 路径中任何一环出错返回默认值 return default # 示例读取配置的工具函数 def load_config(envtest): 加载指定环境的配置 :param env: 环境名称如 dev, test, prod :return: 合并后的配置字典 base_config_path Path(__file__).parent.parent / config / config.yaml env_config_path Path(__file__).parent.parent / config / fconfig_{env}.yaml reader_base YamlReader(base_config_path) base_config reader_base.read() if env_config_path.exists(): reader_env YamlReader(env_config_path) env_config reader_env.read() # 合并配置环境特有配置覆盖基础配置 # 注意这里是浅合并对于嵌套字典需要递归合并可根据需求增强 base_config.update(env_config) return base_config这个工具类提供了两个核心功能read用于读取整个文件get用于通过点分路径如a.b.c精准获取深层嵌套的值这在处理复杂配置时非常方便。load_config函数则演示了如何合并基础配置和环境配置。4. 实战构建数据驱动的接口测试用例现在让我们把理论付诸实践用 pytest 和 YAML 写一个完整的接口测试用例。4.1 准备配置文件首先创建基础配置文件config/config.yaml# 项目基础配置 project: name: API自动化测试项目 version: 1.0 # 请求默认配置 request: base_url: # 基础URL留空由环境配置覆盖 timeout: 10 default_headers: Content-Type: application/json User-Agent: pytest-yaml-demo然后创建测试环境配置config/config_test.yaml# 测试环境特有配置 request: base_url: https://httpbin.org # 这里使用一个公共测试网站4.2 准备测试数据创建用户相关测试数据文件test_data/user_data.yaml# 用户模块测试数据 test_get_user: - case_id: TC_USER_001 description: 获取存在的用户信息 request: method: GET endpoint: /get # httpbin.org的接口 params: id: 1 name: 测试用户A validate: - check: status_code expect: 200 comparator: equals - check: json.args.id expect: 1 comparator: equals - check: json.args.name expect: 测试用户A comparator: equals - case_id: TC_USER_002 description: 获取用户信息-参数为空 request: method: GET endpoint: /get params: {} # 空参数 validate: - check: status_code expect: 200 comparator: equals - check: json.args expect: {} comparator: equals test_post_user: - case_id: TC_USER_003 description: 创建新用户 request: method: POST endpoint: /post json: username: new_user email: newexample.com age: 25 validate: - check: status_code expect: 200 comparator: equals - check: json.json.username expect: new_user comparator: equals这里我们为两个测试场景test_get_user和test_post_user分别准备了一组测试数据每组数据是一个列表里面包含了多个用例字典。每个用例都有唯一的case_id、描述、请求信息和验证规则。4.3 创建核心 Fixture 和请求封装Fixture 是 pytest 的灵魂用于提供测试依赖。我们在项目根目录的conftest.py中定义import pytest import requests from common.read_data import load_config # 读取配置可以通过命令行参数或环境变量控制环境 pytest.fixture(scopesession) def config(): 加载项目配置 env pytest.config.getoption(--env) if hasattr(pytest, config) else test return load_config(env) pytest.fixture(scopesession) def api_client(config): 创建一个配置好基础URL和默认请求头的请求会话 session requests.Session() base_url config.get(request, {}).get(base_url, ) default_headers config.get(request, {}).get(default_headers, {}) class APIClient: def __init__(self, base_url, session): self.base_url base_url self.session session self.session.headers.update(default_headers) def request(self, method, endpoint, **kwargs): 统一的请求方法 url f{self.base_url}{endpoint} if self.base_url else endpoint # 处理超时 timeout kwargs.pop(timeout, config.get(request, {}).get(timeout, 10)) try: response self.session.request(methodmethod, urlurl, timeouttimeout, **kwargs) return response except requests.exceptions.RequestException as e: pytest.fail(f请求失败: {e}) def get(self, endpoint, **kwargs): return self.request(GET, endpoint, **kwargs) def post(self, endpoint, **kwargs): return self.request(POST, endpoint, **kwargs) # 可以继续补充 put, delete 等方法... return APIClient(base_url, session) # 添加命令行选项用于指定运行环境 def pytest_addoption(parser): parser.addoption( --env, actionstore, defaulttest, help指定测试环境: dev, test, prod )这个conftest.py做了几件关键事configfixture根据命令行参数--env加载对应环境的配置作用域为session整个测试会话只执行一次。api_clientfixture提供了一个封装好的请求客户端自动携带基础 URL 和默认请求头并统一了异常处理。pytest_addoption向 pytest 添加了一个自定义命令行选项--env这样我们就能通过pytest --envprod来切换环境了。4.4 编写数据驱动的测试用例最后创建测试用例文件testcases/test_user_api.pyimport pytest from common.read_data import YamlReader # 获取测试数据文件路径 DATA_FILE_PATH test_data/user_data.yaml def load_test_data(data_key): 加载指定键的测试数据 reader YamlReader(DATA_FILE_PATH) test_cases reader.get(data_key, []) if not test_cases: pytest.skip(f测试数据 {data_key} 未找到或为空) # 为每个测试用例生成一个唯一的标识用于报告展示 return [(case.get(case_id, funknown_{i}), case) for i, case in enumerate(test_cases)] class TestUserAPI: 用户相关API测试 pytest.mark.parametrize(case_id, test_data, load_test_data(test_get_user)) def test_get_user(self, api_client, case_id, test_data): 测试GET /get 接口 数据驱动load_test_data(test_get_user) 会返回一组 (case_id, test_data) # 1. 打印用例信息便于调试和报告阅读 print(f\n执行用例: {case_id} - {test_data.get(description)}) # 2. 准备请求参数 req_info test_data[request] method req_info[method] endpoint req_info[endpoint] params req_info.get(params, {}) # 3. 发送请求 response api_client.get(endpoint, paramsparams) # 4. 执行断言 for validation in test_data.get(validate, []): self._do_validation(response, validation) pytest.mark.parametrize(case_id, test_data, load_test_data(test_post_user)) def test_post_user(self, api_client, case_id, test_data): 测试POST /post 接口 print(f\n执行用例: {case_id} - {test_data.get(description)}) req_info test_data[request] method req_info[method] endpoint req_info[endpoint] json_data req_info.get(json, {}) response api_client.post(endpoint, jsonjson_data) for validation in test_data.get(validate, []): self._do_validation(response, validation) def _do_validation(self, response, validation_rule): 统一的断言执行方法 :param response: requests.Response 对象 :param validation_rule: 从YAML中读取的单条验证规则字典 check_type validation_rule.get(check) expected validation_rule.get(expect) comparator validation_rule.get(comparator, equals) # 根据检查类型提取实际值 if check_type status_code: actual response.status_code elif check_type.startswith(json.): # 简单的JSON路径提取实际项目可使用 jsonpath 库 json_path check_type[5:] # 去掉 json. 前缀 actual response.json() for key in json_path.split(.): actual actual.get(key) elif check_type.startswith(headers.): header_key check_type[8:] actual response.headers.get(header_key) else: actual getattr(response, check_type, None) # 根据比较器进行断言 if comparator equals: assert actual expected, f断言失败: {check_type} 期望 {expected}, 实际 {actual} elif comparator contains: assert expected in str(actual), f断言失败: {check_type} 应包含 {expected}, 实际为 {actual} # 可以扩展更多比较器如 greater_than, less_than, not_equals 等这个测试类展示了数据驱动的精髓load_test_data函数从 YAML 文件中读取特定键下的所有测试用例。pytest.mark.parametrize装饰器将读取到的用例列表展开pytest 会为列表中的每个元素即每个用例单独运行一次被装饰的测试函数。测试函数接收api_clientfixture 以及解包后的case_id和test_data。函数内部根据test_data构造请求并发送然后根据validate规则进行断言。统一的_do_validation方法处理不同类型的断言使得 YAML 中的断言规则可以灵活扩展。4.5 运行与报告现在在项目根目录下运行测试# 运行所有测试 pytest # 运行特定测试类 pytest testcases/test_user_api.py # 运行特定测试方法 pytest testcases/test_user_api.py::TestUserAPI::test_get_user # 指定测试环境并生成HTML报告 pytest --envtest --htmlreport.html --self-contained-html运行后你会看到每个用例独立执行和断言。生成的report.html报告会清晰展示每个用例通过case_id标识的执行结果极大方便了结果回溯和失败分析。5. 高级技巧与最佳实践掌握了基础用法后我们来看看如何让这个组合更加强大和稳健。5.1 使用 Fixture 动态生成测试数据有时测试数据需要动态生成比如依赖前一个接口的返回。我们可以结合 Fixture 来实现import pytest import yaml pytest.fixture def dynamic_user_data(): 动态生成用户测试数据例如从数据库或上游接口获取ID # 这里模拟一个动态过程 base_data { username: dynamic_user, email: dynamicexample.com } # 假设我们从某个地方获取了一个动态ID dynamic_id fetch_latest_user_id() # 假设的函数 base_data[id] dynamic_id return base_data def test_with_dynamic_data(api_client, dynamic_user_data): 使用动态生成的数据进行测试 response api_client.post(/post, jsondynamic_user_data) assert response.status_code 200 # ... 更多断言然后你甚至可以将这个动态数据写入一个临时的 YAML 文件供其他用例读取或者直接作为参数传递给其他 Fixture。5.2 复杂断言与 JSONPath 集成前面例子中的 JSON 提取比较简单。对于复杂的 JSON 响应使用jsonpath库会强大得多。首先安装它pip install jsonpath-ng。然后增强你的断言工具from jsonpath_ng import parse def extract_by_jsonpath(json_data, jsonpath_expr): 使用jsonpath从JSON数据中提取值 expr parse(jsonpath_expr) matches [match.value for match in expr.find(json_data)] return matches[0] if matches else None # 在 _do_validation 方法中可以这样使用 if check_type.startswith($.): # 使用 $ 开头表示jsonpath actual extract_by_jsonpath(response.json(), check_type)这样在 YAML 中你就可以使用强大的 JSONPath 表达式了validate: - check: $.data.users[0].name expect: 张三 comparator: equals - check: $.data.orders[*].totalPrice expect: 100 comparator: greater_than # 假设我们扩展了这个比较器5.3 测试数据与用例的关联管理当项目变大数据文件增多时需要更好的管理策略按业务域分文件如user_data.yaml,order_data.yaml,product_data.yaml。使用数据标识符在每个用例数据中保留唯一的case_id便于在测试报告、缺陷管理系统中追踪。数据版本化将测试数据文件也纳入 Git 管理并通过 Tag 或分支来关联特定版本的测试数据与代码。数据准备与清理对于创建数据的测试在 YAML 中或通过 Fixture 明确标出需要清理的数据标识并在测试后自动清理保证环境干净。5.4 配置的继承与覆盖对于多环境配置简单的update合并可能不够特别是嵌套字典。我们可以实现一个递归合并函数def deep_merge(base, override): 深度合并两个字典override中的值会覆盖base中的值 for key, value in override.items(): if key in base and isinstance(base[key], dict) and isinstance(value, dict): # 如果两者都是字典则递归合并 deep_merge(base[key], value) else: # 否则直接覆盖或新增 base[key] value return base在load_config函数中使用deep_merge来代替简单的update可以更智能地处理嵌套配置。6. 常见问题与排查技巧实录在实际使用中你肯定会遇到一些坑。这里记录了几个典型问题和我踩过之后的解决方案。6.1 YAML 语法错误缩进与特殊字符问题运行测试时yaml.safe_load抛出YAMLError提示映射值或缩进错误。原因这是 YAML 文件编写中最常见的问题。使用了 Tab 缩进YAML 规定只能用空格。缩进不一致比如混用了 2 个空格和 4 个空格。特殊字符未转义包含冒号:、井号#等特殊字符的字符串未加引号。排查与解决用编辑器的“显示空格/制表符”功能检查文件。推荐使用 VS Code 或 PyCharm它们对 YAML 有很好的语法高亮和校验。确保整个文件使用统一的缩进建议 2 个空格。对于包含:、#、{、}、[、]、,、、*的字符串值务必用单引号或双引号包裹。# 错误 message: 成功: 操作完成 # 正确 message: 成功: 操作完成6.2 测试数据未找到或参数化失败问题测试运行时跳过或报错提示测试数据未找到或者pytest.mark.parametrize接收到的参数为空。原因YAML 文件路径错误。YAML 文件中对应的数据键key不存在或拼写错误。load_test_data函数返回的数据格式不是list of tuples或list of dicts不符合parametrize的预期。排查与解决打印调试在load_test_data函数中加入print(f“Loading key: {data_key} from {DATA_FILE_PATH}”)和print(f“Loaded data: {test_cases}”)查看实际加载到了什么。检查文件与键名双重检查文件路径和你在pytest.mark.parametrize中传入的键名是否与 YAML 文件中的一级键完全一致包括大小写。验证数据结构确保test_cases是一个列表。如果 YAML 中你的数据是字典而不是列表需要调整。parametrize期望一个可迭代对象其中每个元素会成为测试函数的一组参数。6.3 环境配置切换不生效问题通过--env参数指定了环境但测试似乎仍然在使用旧的配置比如 base_url 没变。原因conftest.py中的pytest_addoption没有被正确识别。确保conftest.py在项目根目录或测试目录下并且文件名拼写正确。configfixture 中获取命令行参数的方式不对。在较新版本的 pytest 中推荐使用request.config.getoption。配置合并逻辑有误环境配置没有成功覆盖基础配置。排查与解决检查 conftest.py 位置确保它在项目根目录。更新 config fixture修改conftest.py中的configfixture 定义pytest.fixture(scopesession) def config(request): # 注入 request fixture env request.config.getoption(--env) return load_config(env)打印最终配置在测试开始时临时打印一下加载到的配置确认 base_url 是否正确def test_something(api_client, config): print(f“当前配置 base_url: {config.get(request, {}).get(base_url)}”) # ... 测试逻辑6.4 测试报告中的用例名称不友好问题生成的 HTML 报告或控制台输出中用例名称显示为test_get_user[TC_USER_001-...]这种冗长格式可读性差。原因pytest.mark.parametrize默认使用参数值的repr()形式作为用例 ID。解决使用ids参数自定义用例标识。def load_test_data(data_key): reader YamlReader(DATA_FILE_PATH) test_cases reader.get(data_key, []) if not test_cases: pytest.skip(f“测试数据 {data_key} 未找到或为空”) # 返回两个列表用例数据列表 和 ID 列表 case_ids [case.get(case_id, funknown_{i}) for i, case in enumerate(test_cases)] return test_cases, case_ids # 在测试类中 class TestUserAPI: test_cases, case_ids load_test_data(test_get_user) pytest.mark.parametrize(test_data, test_cases, idscase_ids) def test_get_user(self, api_client, test_data): # ... 函数体内通过 test_data[case_id] 获取ID print(f“执行用例: {test_data[case_id]}”)这样报告中的用例名就会显示为test_get_user[TC_USER_001]清晰多了。6.5 性能问题大量YAML文件导致测试启动慢问题当测试数据和配置文件非常多时每次运行测试都读取和解析所有 YAML 文件可能导致测试启动缓慢。原因YamlReader.read()在每次调用时都进行文件 IO 和解析。优化使用缓存对配置等不常变的数据使用lru_cache进行内存缓存。from functools import lru_cache class YamlReader: def __init__(self, yaml_file): self.yaml_file yaml_file lru_cache(maxsizeNone) def read(self): with open(self.yaml_file, r, encodingutf-8) as f: return yaml.safe_load(f) or {}这样在同一进程内多次读取同一个文件只会解析一次。按需加载不要在一开始就加载所有数据文件。只在具体的测试模块或测试函数中加载其所需的数据。考虑序列化对于极其庞大且稳定的测试数据可以考虑将其转换为 Python 的.py文件或.pkl文件加载速度会更快但这牺牲了可读性和直接编辑的便利性需权衡。7. 项目扩展与进阶方向当你熟练运用 pytest YAML 的基础模式后可以考虑以下几个方向来扩展你的测试框架使其更专业、更强大。7.1 集成 Allure 报告生成更炫酷的报告pytest-html 报告简单实用但 Allure 报告在美观度和信息整合上更胜一筹。安装pip install allure-pytest。运行测试pytest --alluredir./allure-results。生成报告allure serve ./allure-results需要先安装 Allure 命令行工具。在 YAML 测试数据中可以添加额外的字段来丰富 Allure 报告- case_id: TC_001 description: 一个重要的测试用例 allure: epic: 用户模块 feature: 登录功能 story: 正常登录流程 severity: blocker request: ... validate: ...然后在测试函数中使用allure注解动态添加这些信息。7.2 实现测试步骤与日志记录对于复杂的业务流程测试将测试步骤记录到日志和报告中非常有用。可以结合pytest的fixture和 Python 的logging模块在api_client.request方法中自动记录请求和响应的摘要信息。也可以使用allure.step来在 Allure 报告中标记出关键步骤。7.3 搭建持续集成流水线将你的 pytest YAML 测试项目集成到 CI/CD 流水线中如 Jenkins、GitLab CI、GitHub Actions。核心步骤通常包括代码检出。安装依赖 (pip install -r requirements.txt)。运行测试 (pytest --envtest --alluredirallure-results)。生成并归档测试报告。根据测试结果决定流水线成功或失败。在 CI 环境中通常通过环境变量来传递配置如数据库密码、密钥而不是写在 YAML 文件里。你的load_config函数需要能够读取环境变量来覆盖文件中的配置。7.4 数据工厂与动态数据生成对于需要大量随机、合规测试数据的场景如性能测试、边界测试可以引入“数据工厂”概念。例如使用Faker库 (pip install faker) 在 Fixture 中动态生成用户数据import pytest from faker import Faker fake Faker(zh_CN) pytest.fixture def random_user_data(): return { name: fake.name(), email: fake.email(), phone: fake.phone_number(), address: fake.address() }然后将这个 fixture 与 YAML 中的模板数据结合实现“模板变量”的数据生成模式。踩过不少坑之后我最大的体会是pytest YAML 这套组合的威力不在于用了多高深的技术而在于它强制你建立起一种清晰、分离的测试结构思维。一旦你习惯了将数据、配置、脚本分开管理测试代码的维护性会指数级提升。刚开始可能会觉得多写 YAML 文件有点麻烦但当你需要修改大量测试数据或者需要为同一套脚本准备多套环境配置时你会庆幸自己当初的选择。最后一个小技巧是给你的 YAML 文件也写一个简单的 schema 说明或者模板文件放在项目文档里这样团队新成员就能快速上手保持数据格式的统一。