在自动化测试开发中,频繁的登录操作会显著降低测试效率。每次测试用例都重新登录不仅浪费时间,还会给认证服务器带来不必要的压力。本文将介绍如何通过pytest的session级fixture实现token的全局复用,让整套测试流程只需登录一次。
这个方案特别适合企业级测试场景,比如:
我在金融科技公司的测试实践中,采用这种方案后,测试套件执行时间从原来的45分钟缩短到32分钟,效率提升近30%。更重要的是,它解决了因频繁登录导致的账号被临时锁定问题。
pytest的fixture系统支持多种作用域控制,其中scope="session"表示该fixture在整个测试会话期间只会执行一次。相比默认的function级别(每个测试函数执行一次),session级fixture具有以下特点:
要实现安全的token复用,需要考虑以下几个关键点:
在项目根目录或tests目录下创建conftest.py文件:
python复制import pytest
from common.auth import login
@pytest.fixture(scope="session")
def session_token():
"""
会话级token fixture
返回:(tenant_key, tenant_id, token) 三元组
"""
tenant_key = "A" # 示例使用固定租户,实际项目应改为动态获取
# 调用认证模块获取token
tenant_id, token = login(tenant_key)
# 登录失败时立即终止测试会话
if not token:
pytest.exit("登录失败,无法获取有效token")
return tenant_key, tenant_id, token
关键说明:
conftest.py是pytest的专用配置文件,会自动被发现scope="session"确保fixture在整个测试会话中只执行一次pytest.exit()比assert更适合用于终止测试会话创建测试文件tests/test_token_demo.py:
python复制def test_case_1(session_token):
"""测试用例1"""
tenant_key, tenant_id, token = session_token
print(f"用例1 token: {token}")
# 实际测试中这里会使用token调用API
assert token is not None
def test_case_2(session_token):
"""测试用例2"""
tenant_key, tenant_id, token = session_token
print(f"用例2 token: {token}")
# 可以验证token是否与用例1相同
assert token == test_case_1.token if hasattr(test_case_1, 'token') else None
执行测试命令:
bash复制pytest -s -v tests/test_token_demo.py
预期输出应该显示:
实际企业环境中,可能需要支持多租户测试。可以改造fixture如下:
python复制@pytest.fixture(scope="session")
def session_token(request):
"""支持通过命令行参数指定租户"""
tenant_key = request.config.getoption("--tenant") or "A"
tenant_id, token = login(tenant_key)
if not token:
pytest.exit(f"租户{tenant_key}登录失败")
return tenant_key, tenant_id, token
然后通过命令行指定租户:
bash复制pytest --tenant=B tests/test_token_demo.py
对于长时间运行的测试套件,可以实现token自动刷新:
python复制@pytest.fixture(scope="session")
def session_token():
tenant_key = "A"
tenant_id, token = login(tenant_key)
# 记录token获取时间
last_refresh = time.time()
def get_token():
nonlocal token, last_refresh
# 超过1小时自动刷新
if time.time() - last_refresh > 3600:
tenant_id, token = login(tenant_key)
last_refresh = time.time()
return token
return tenant_key, tenant_id, get_token
通过环境变量控制不同环境的认证配置:
python复制import os
@pytest.fixture(scope="session")
def session_token():
env = os.getenv("TEST_ENV", "dev")
tenant_key = f"A_{env.upper()}" # 如A_DEV, A_PROD
auth_url = {
"dev": "https://dev.auth.example.com",
"prod": "https://auth.example.com"
}[env]
tenant_id, token = login(tenant_key, auth_url)
return tenant_key, tenant_id, token
现象:测试运行中途token失效,后续用例失败
解决方案:
python复制@retry(tries=3, delay=1)
def safe_login(tenant_key):
return login(tenant_key)
现象:多个测试进程使用相同token导致冲突
解决方案:
--dist=loadscope选项python复制def pytest_configure(config):
if hasattr(config, 'workerinput'):
# 分布式worker环境下生成worker特定token
config.worker_token = generate_token_for_worker()
现象:一个测试修改了全局状态影响其他测试
解决方案:
python复制@pytest.fixture(autouse=True)
def reset_state(session_token):
clean_test_data(session_token[1])
yield
clean_test_data(session_token[1])
对于CI/CD环境,可以缓存登录token:
python复制@pytest.fixture(scope="session")
def session_token(tmp_path_factory):
cache_file = tmp_path_factory.getbasetemp() / "token_cache.json"
if cache_file.exists():
# 从缓存读取
data = json.loads(cache_file.read_text())
if time.time() - data["timestamp"] < 3600:
return data["tenant_key"], data["tenant_id"], data["token"]
# 正常登录流程
tenant_key, tenant_id, token = do_login()
# 写入缓存
cache_file.write_text(json.dumps({
"tenant_key": tenant_key,
"tenant_id": tenant_id,
"token": token,
"timestamp": time.time()
}))
return tenant_key, tenant_id, token
结合pytest-xdist实现并行测试:
bash复制pytest -n 4 --dist=loadscope tests/
对应的fixture调整:
python复制@pytest.fixture(scope="session")
def session_token(request):
if hasattr(request.config, 'workerinput'):
# 每个worker独立登录
worker_id = request.config.workerinput['workerid']
return login(f"A_W{worker_id}")
return login("A")
Token保护:
账号隔离:
网络传输安全:
在实际项目中,我们还会在fixture中添加审计日志:
python复制@pytest.fixture(scope="session")
def session_token():
tenant_key = "A"
audit_log(f"测试会话启动,使用租户: {tenant_key}")
try:
tenant_id, token = login(tenant_key)
audit_log(f"登录成功,租户ID: {tenant_id}")
return tenant_key, tenant_id, token
except Exception as e:
audit_log(f"登录失败: {str(e)}")
pytest.exit("认证失败")
finally:
audit_log("测试会话结束")