pytest通过pytest_addoption注册--env命令行参数,并在conftest.py中定义scope="session"的env fixture获取该值,使所有测试函数一致获取环境标识,再据此动态加载对应环境的YAML/JSON数据文件,路径基于__file__相对定位并校验存在性,敏感字段通过环境变量替换或密钥服务注入。

pytest如何通过命令行参数区分测试环境
pytest本身不内置环境变量管理,但支持用--env这类自定义命令行参数传递环境标识,再在conftest.py里读取并注入到测试上下文。关键不是“怎么设变量”,而是“怎么让所有测试函数都能一致拿到它”。
在conftest.py中注册参数并提供fixture:
def pytest_addoption(parser):
parser.addoption(
"--env",
action="store",
default="dev",
help="test environment: dev/staging/prod"
)
<p>@pytest.fixture(scope="session")
def env(request):
return request.config.getoption("--env")</p>
这样每个测试函数只要声明env参数就能拿到值,比如def test_login(env):。
- 必须用
scope="session",否则每次调用fixture都重新解析参数,可能出错 -
default="dev"建议设成最常跑的环境,避免漏传参数时直接报错 - 不要在
pytest_addoption里做数据加载——此时配置文件可能还没读,只负责“登记参数”
用env fixture动态加载YAML/JSON测试数据文件
数据文件按环境分目录放,比如testdata/dev/users.yaml、testdata/prod/orders.json,然后在fixture里拼路径加载:
@pytest.fixture(scope="session")
def test_data(env):
import yaml
path = f"testdata/{env}/users.yaml"
with open(path) as f:
return yaml.safe_load(f)
注意路径拼接必须严格匹配实际结构,env值直接参与路径构造,所以env不能含特殊字符或路径遍历符号(如../)。
- 推荐在加载前加存在性校验:
assert os.path.exists(path),避免静默失败 - 如果不同环境数据结构差异大,别强行复用同一个fixture名,可拆成
dev_users、prod_orders等明确命名的fixture - YAML加载用
yaml.safe_load,不用load,防止恶意数据执行代码
避免硬编码路径导致CI/本地运行不一致
测试数据路径写死会导致CI机器找不到文件,尤其当项目根目录和工作目录不一致时。必须用Path(__file__).parent.parent这类相对定位,而不是./testdata。
正确做法是把数据路径封装成一个fixture:
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
@pytest.fixture(scope="session")
def data_root():
return Path(__file__).parent / "testdata"
再组合使用:
@pytest.fixture(scope="session")
def test_data(env, data_root):
path = data_root / env / "users.yaml"
# ...
-
__file__指向conftest.py所在位置,以此为基准找testdata才可靠 - CI里常用
cd /tmp/project && pytest tests/,此时./testdata会指向/tmp/project/testdata,但若没同步该目录就404 - Pytest的
--rootdir参数会影响__file__行为,但用Path(__file__).parent基本不受影响
敏感数据(如密码)不能进Git,怎么安全加载?
开发环境用明文YAML没问题,但staging/prod的账号密码绝不能提交。这时要分两层:基础数据走文件,敏感字段走环境变量或密钥服务。
示例:YAML里留占位符,运行时替换:
# testdata/prod/api.yaml
url: https://api.example.com
auth:
user: "${API_USER}"
password: "${API_PASSWORD}"
加载时用os.environ.get()替换:
import os
import re
<p>def substitute_env_vars(content):
for key, val in os.environ.items():
content = re.sub(rf"\${{{key}}}", val, content)
return content</p>
- 别用
string.Template,它对$符号太敏感,容易误替换 - CI里通过
export API_USER=xxx或Secrets机制注入,本地用.env文件+python-dotenv加载 - 如果密钥来自Vault/AWS Secrets Manager,这部分逻辑应单独抽成
secrets_clientfixture,不和普通test_data混在一起
真正麻烦的不是怎么换变量,而是确保所有环境都配齐了对应变量——漏一个,测试就卡在None上,还不好定位。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










