目录[-]
一、背景
在智能云科负责iSESOL BIZ项目时,遇到几个痛点:
- 接口数量多(300+),手工回归耗时2天
- 接口之间依赖复杂(登录token、订单号传递)
- 业务场景多样(5种询盘单、12种订单模式)
- 开发频繁改动,需要快速回归验证
于是设计了一套基于POM分层架构的接口自动化框架,核心目标:
分层设计:基础层、业务层、用例层分离
可复用:接口方法一次封装,到处调用
可维护:接口变更只改一处
可观测:集成Allure报告,一目了然
二、技术架构
|
层级 |
模块 |
技术栈 |
职责 |
|
基础层 |
api_handler.py |
requests + allure + logging |
封装HTTP请求、装饰器、配置管理 |
|
业务层 |
isesol.py |
继承配置类 |
封装具体业务接口(登录、询盘、订单等) |
|
用例层 |
test_isesol.py |
pytest + allure |
编写测试用例、断言、报告 |
|
工具层 |
log_config.py |
logging |
日志管理、控制台+文件输出 |
|
执行层 |
run_tests.py |
pytest + allure |
批量执行、生成报告 |
整体目录结构:
class SimpleConfig:
configs = {
"isesol": {
"url": "https://api.isesol.com/cgi/",
"headers": {
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json;charset=UTF-8",
"User-Agent": "Mozilla/5.0 ..."
}
}
}
@classmethod
def get(cls, name):
return cls.configs.get(name, {})
三、核心代码实现
1. 配置管理(SimpleConfig)
最简单的方式,把API配置存在类变量里:
def handle_api_request(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
# 获取请求数据
params = func(self, *args, **kwargs)
# 发送请求
response = requests.post(
url=params.get("url", self.url),
headers=final_headers,
json=params.get("data", {}),
timeout=30
)
# 记录到Allure
with allure.step(f"执行 {func.__name__}"):
allure.attach(json.dumps(request_data, indent=2),
name="请求参数")
# 处理响应
if hasattr(self, 'modify_resp'):
return self.modify_resp(response)
return response.json()
return wrapper
2. 请求装饰器(核心)
用一个装饰器统一处理:请求日志、Allure报告、异常捕获、响应处理:
def handle_api_request(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
# 获取请求数据
params = func(self, *args, **kwargs)
# 发送请求
response = requests.post(
url=params.get("url", self.url),
headers=final_headers,
json=params.get("data", {}),
timeout=30
)
# 记录到Allure
with allure.step(f"执行 {func.__name__}"):
allure.attach(json.dumps(request_data, indent=2),
name="请求参数")
# 处理响应
if hasattr(self, 'modify_resp'):
return self.modify_resp(response)
return response.json()
return wrapper
3. 业务接口封装
每个接口封装成一个方法,返回值给装饰器处理:
class isesol_Main:
def __init__(self, api_name="isesol"):
config = SimpleConfig.get(api_name)
self.headers = config.get("headers", {}).copy()
self.url = config.get("url", "")
@handle_api_request
def getRecommendEnquiryList(self):
"""获取推荐询价列表"""
return {
"data": {
"cmd": "gateway_basewebfront/enquiry/getRecommendEnquiryList"
}
}
@handle_api_request
def queryIndexBanner(self):
"""查询首页Banner"""
return {
"data": {
"cmd": "gateway_basewebfront/webBanner/queryIndexBanner"
}
}
4. 日志配置
同时输出到控制台和文件,按日期分割:
def setup_logging(log_dir="./logs", level=logging.INFO):
log_file = os.path.join(log_dir, f"test_{datetime.now().strftime('%Y%m%d')}.log")
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler = logging.FileHandler(log_file, encoding='utf-8')
console_handler = logging.StreamHandler()
logger = logging.getLogger("api_test")
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
5. 测试用例编写
使用pytest + allure,一个接口一个测试方法:
@allure.feature("ISESOL API测试")
class TestIsesolAPI:
def setup_class(self):
self.api = isesol.isesol_Main()
@allure.story("推荐询价列表接口")
@allure.title("测试获取推荐询价列表")
def test_get_recommend_enquiry_list(self):
result = self.api.getRecommendEnquiryList()
assert result is not None
6. 执行入口
批量运行、自动清理旧报告、生成Allure报告:
if __name__ == "__main__":
# 清理旧报告
for folder in ["./allure_results", "./html_result"]:
if os.path.exists(folder):
shutil.rmtree(folder)
os.makedirs(folder, exist_ok=True)
# 执行测试
pytest.main([
"test_cases/",
"-v",
"-s",
"--alluredir=./allure_results"
])
# 生成HTML报告
os.system("allure generate ./allure_results -o ./html_result/ --clean --single-file")
四、踩坑记录
坑1:装饰器内self丢失
- 现象:装饰器内访问不到实例的 self.headers
- 解决:装饰器包装的是实例方法,wrapper 的第一个参数是 self,直接使用即可
坑2:配置被多个实例修改
- 现象:一个实例改了headers,其他实例也受影响
- 解决:self.headers = config.get("headers", {}).copy(),复制一份
坑3:Allure报告中文乱码
- 现象:Allure报告里中文显示为 ???
- 解决:allure.attach(json.dumps(data, indent=2, ensure_ascii=False)) 加 ensure_ascii=False
坑4:requests请求被代理拦截
- 现象:本地开了代理,请求走代理导致失败
- 解决:proxies={"http": None, "https": None} 禁用代理
坑5:响应格式不统一
- 现象:有的接口返回JSON,有的返回纯文本
- 解决:在 modify_resp 里统一处理,try: return response.json() except: return {}
五、优化方向
- 支持更多HTTP方法:当前只支持POST,可扩展GET、PUT、DELETE
- 支持动态参数:{ "timestamp": "$now" } 自动替换
- 接口依赖管理:登录token自动传递,无需每个用例手动处理
- 数据驱动:测试数据放在Excel/YAML,用例层只写逻辑
- 支持多环境切换:dev/test/prod配置分开
六、使用示例
# 安装依赖
pip install -r requirements.txt
# 运行测试
python run_tests.py
# 查看报告
allure open html_result
运行后,Allure报告会自动生成在 html_result/ 目录,打开后可以看到:
- 每个接口的请求参数、响应数据
- 测试步骤、日志
- 失败原因、截图(可扩展)
七、总结
这套框架的核心思想是:让测试人员只关注业务逻辑,不关心HTTP细节。
- 新人入职,只需要在 cases/ 目录下新增接口方法,在 test_cases/ 下写断言
- 接口变更,只改 cases/ 里对应的封装,用例层不用动
- 框架维护者(我)负责 base/ 和 run_tests.py,其他人只用干活
分层设计的价值就在这里:把复杂度关在笼子里,把简单留给使用者。