在接口自动化测试中,Session会话保持是一个经常被忽视但极其关键的技术点。做过Web应用测试的同行们都知道,很多业务场景下(比如用户登录后的操作流程),服务器需要通过Session来识别和跟踪用户状态。如果我们的自动化测试脚本无法正确处理Session,就会导致每次请求都被当作新用户,轻则测试数据不准确,重则直接测试失败。
我经历过一个真实案例:某电商平台的购物车功能测试,由于脚本没有保持Session,每次添加商品都创建了新的匿名用户购物车,导致断言永远失败。后来加入Session保持后,不仅测试通过率从60%提升到98%,还能真实模拟用户完整操作路径。
当客户端第一次访问服务端时,服务端会通过Set-Cookie响应头返回一个名为sessionid(不同框架可能名称不同)的Cookie。以Django为例,默认的Session Cookie名称是sessionid,而Flask则是session。这个Cookie值就是服务端识别客户端身份的凭证。
关键点在于:后续请求必须原样带回这个Cookie,服务端才能找到对应的Session数据。这就是为什么我们常说"Session基于Cookie实现"——但严格来说,Session数据存储在服务端,Cookie只是钥匙。
不同Web框架的Session存储策略差异很大:
理解被测系统的Session实现方式非常重要。比如测试Flask应用时,如果修改了SECRET_KEY,之前所有的Session都会立即失效。
python复制import requests
# 创建Session实例
session = requests.Session()
# 首次登录获取Session
login_url = "https://api.example.com/login"
login_data = {"username": "test", "password": "123456"}
response = session.post(login_url, json=login_data)
# 后续请求自动保持Session
profile_url = "https://api.example.com/user/profile"
profile_response = session.get(profile_url) # 自动携带登录后的Cookie
这个简单的例子展示了requests.Session()的核心价值:自动处理Cookie的存储和回传。实测发现,使用Session对象后,需要手动处理Cookie的代码量减少70%以上。
python复制from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# 配置重试策略
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
# 应用到Session
session = requests.Session()
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)
这种配置下,当遇到5xx错误时会自动重试,特别适合测试环境不稳定的情况。根据我的经验,合理设置backoff_factor能有效避免"重试风暴"。
python复制# 定义响应处理钩子
def log_response(resp, *args, **kwargs):
print(f"Response: {resp.status_code} {resp.url}")
return resp
# 注册钩子
session = requests.Session()
session.hooks["response"] = [log_response]
钩子函数非常适合用于:
当测试涉及多个子域名(如api.example.com和auth.example.com)时,需要注意:
python复制from http.cookiejar import DefaultCookiePolicy
policy = DefaultCookiePolicy(
allowed_domains=['.example.com'], # 允许的域名
strict_domain=False # 不严格匹配子域名
)
session.cookies.set_policy(policy)
在并行测试场景下,多个worker需要共享登录状态。解决方案有:
python复制# 主进程
master_session = requests.Session()
master_session.post(login_url, data=credentials)
cookies = master_session.cookies.get_dict()
# worker进程
worker_session = requests.Session()
worker_session.cookies.update(cookies)
python复制# 获取Token
auth_response = requests.post(auth_url, data=credentials)
token = auth_response.json()['token']
# 后续请求携带Token
headers = {'Authorization': f'Bearer {token}'}
response = requests.get(api_url, headers=headers)
python复制# conftest.py
import pytest
import requests
@pytest.fixture(scope="module")
def auth_session():
"""模块级共享的认证Session"""
session = requests.Session()
yield session
session.close() # 测试结束后清理
# 测试用例
def test_user_profile(auth_session):
response = auth_session.get(profile_url)
assert response.status_code == 200
assert response.json()['username'] == 'test'
这种模式的优势:
python复制import allure
@allure.step("用户登录")
def login(session, username, password):
response = session.post(
login_url,
json={"username": username, "password": password}
)
assert response.status_code == 200
return response
def test_login_flow():
with allure.step("初始化Session"):
session = requests.Session()
with allure.step("执行登录"):
login(session, "test", "123456")
with allure.step("验证权限"):
response = session.get(profile_url)
assert 'admin' in response.json()['roles']
这样生成的Allure报告会清晰展示每个Session相关的操作步骤,极大方便排查问题。
python复制from requests.adapters import HTTPAdapter
session = requests.Session()
# 配置连接池
adapter = HTTPAdapter(
pool_connections=10, # 连接池大小
pool_maxsize=50, # 最大连接数
max_retries=3 # 重试次数
)
session.mount('https://', adapter)
合理配置连接池可以:
python复制# 生产环境必须开启
session.verify = '/path/to/cert.pem'
# 测试环境可临时关闭(不推荐)
session.verify = False
python复制# 使用环境变量而非硬编码
import os
session.auth = (
os.getenv('API_USER'),
os.getenv('API_PASS')
)
python复制# 每100次请求更换Session
if request_count % 100 == 0:
session.close()
session = requests.Session()
re_login(session)
python复制# 获取JWT Token
auth_response = requests.post(
auth_url,
json={"username": "test", "password": "123456"}
)
token = auth_response.json()['access_token']
# 使用Token访问API
headers = {'Authorization': f'Bearer {token}'}
response = requests.get(api_url, headers=headers)
优势:
python复制# 获取授权码
auth_code = get_authorization_code()
# 交换Token
token_response = requests.post(
token_url,
data={
'grant_type': 'authorization_code',
'code': auth_code,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
}
)
# 使用Access Token
access_token = token_response.json()['access_token']
session = requests.Session()
session.headers.update({'Authorization': f'Bearer {access_token}'})
测试OAuth2.0时需要注意:
python复制import logging
# 启用调试日志
logging.basicConfig(level=logging.DEBUG)
# 发起请求时会显示完整HTTP交互
session.get("https://api.example.com")
输出示例:
code复制DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.example.com:443
DEBUG:urllib3.connectionpool:https://api.example.com:443 "GET / HTTP/1.1" 200 1234
python复制proxies = {
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
}
session = requests.Session()
session.proxies = proxies
session.verify = False # 忽略证书错误
配合mitmproxy可以:
重要提示:代理工具仅用于测试环境,生产环境必须移除所有代理配置
建议实现一个统一的Session管理器:
python复制class SessionManager:
_instances = {}
@classmethod
def get_session(cls, env="prod"):
if env not in cls._instances:
session = requests.Session()
# 初始化配置...
cls._instances[env] = session
return cls._instances[env]
@classmethod
def cleanup(cls):
for session in cls._instances.values():
session.close()
cls._instances.clear()
# 使用示例
def test_case():
session = SessionManager.get_session("test")
response = session.get("/api")
# ...
这种设计模式的优势:
在CI/CD流水线中,Session管理需要特别考虑:
典型实现方案:
python复制# Jenkins Pipeline示例
pipeline {
agent any
environment {
TEST_SESSION_ID = UUID.randomUUID().toString()
}
stages {
stage('Test') {
steps {
sh 'python -m pytest --session-id=${TEST_SESSION_ID}'
}
}
}
post {
always {
sh 'python cleanup.py ${TEST_SESSION_ID}'
}
}
}
虽然本文重点讨论了传统的Session管理方式,但在实际项目中,我们正在见证一些重要转变:
对于测试工程师来说,这意味着: