在软件测试领域,接口自动化测试是保障系统稳定性、提升测试效率的关键手段。随着敏捷开发和DevOps理念的普及,自动化测试的重要性愈发凸显。Python凭借其简洁的语法、丰富的库生态,成为接口自动化测试的首选语言;Requests库让HTTP请求的发送变得简单高效;Pytest则以其灵活的用例管理、强大的断言机制和丰富的插件生态,成为测试框架的佼佼者。本文将从专业角度,详细介绍如何基于Python+Requests+Pytest搭建一套高效、可扩展的接口自动化测试框架。

一、框架搭建前的准备工作

1.1 环境配置

工欲善其事,必先利其器。搭建框架前,需要确保开发环境的正确配置:

  • Python环境:推荐使用Python 3.8及以上版本,该版本在Windows、MacOS和Linux系统上均有良好的兼容性,且能较好地支持后续所需的第三方库。可以通过Python官方网站下载安装包,或使用Pyenv、Miniconda等工具进行版本管理,避免不同项目之间的环境冲突。

  • 虚拟环境创建:为了隔离不同项目的依赖,建议创建虚拟环境。在项目根目录下,执行以下命令:

    python -m venv venv
    # Linux/Mac系统激活虚拟环境
    source venv/bin/activate
    # Windows系统激活虚拟环境
    venv\Scripts\activate.bat

  • 核心依赖安装:激活虚拟环境后,安装框架所需的核心库。执行以下命令:

    pip install pytest==7.4.0 requests==2.31.0 allure-pytest==2.13.2 pytest-html==4.1.1

    • pytest:测试框架核心,用于用例管理、执行和断言。

    • requests:发送HTTP请求的库,支持GET、POST、PUT、DELETE等多种请求方式。

    • allure-pytest:生成Allure测试报告的插件,提供美观、详细的可视化报告。

    • pytest-html:生成HTML格式的测试报告,方便快速查看测试结果。

1.2 项目目录结构设计

合理的目录结构是框架可维护性和扩展性的基础。经过多个项目的实践验证,以下目录结构具有较高的实用性:

api_auto_test/
├── config/ # 配置文件目录
│ ├── __init__.py
│ ├── global_config.py # 全局配置变量,如环境地址、超时时间等
│ └── pytest.ini # Pytest配置文件
├── testcases/ # 测试用例目录
│ ├── __init__.py
│ ├── test_user_api.py # 用户模块接口测试用例
│ └── test_order_api.py # 订单模块接口测试用例
├── utils/ # 工具类目录
│ ├── __init__.py
│ ├── request_util.py # 请求工具类,封装HTTP请求方法
│ └── assert_util.py # 断言工具类,封装自定义断言方法
├── resources/ # 测试数据目录
│ ├── __init__.py
│ ├── user_data.yaml # 用户模块测试数据
│ └── order_data.json # 订单模块测试数据
├── reports/ # 测试报告目录
│ ├── html/ # HTML报告存放路径
│ └── allure/ # Allure报告原始数据存放路径
├── logs/ # 日志文件目录
├── requirements.txt # 项目依赖清单
└── run.py # 测试执行入口
  • config目录:存放全局配置信息,通过global_config.py统一管理不同环境的地址、接口超时时间等参数,方便环境切换。pytest.ini用于配置Pytest的运行参数,如测试用例发现规则、报告生成路径等。

  • testcases目录:按照业务模块划分测试用例,每个模块对应一个测试文件,便于维护和扩展。

  • utils目录:封装通用的工具方法,如请求发送、断言验证等,提高代码复用率。

  • resources目录:存放测试数据,支持YAML、JSON等格式,实现测试数据与代码的分离,便于非技术人员维护测试数据。

  • reports目录:存放测试报告,分为HTML和Allure两种格式,满足不同场景的需求。

  • logs目录:记录测试执行过程中的日志信息,便于问题排查。

  • requirements.txt:记录项目依赖的库及其版本,方便团队成员快速搭建一致的开发环境。

  • run.py:作为测试执行的入口,通过代码方式配置Pytest的运行参数,实现一键执行测试。

二、核心模块封装

2.1 请求工具类封装

Requests库虽然使用简单,但在实际项目中,直接使用其原生方法会导致代码冗余,且不利于统一管理请求头、超时时间、异常处理等。因此,需要对Requests进行二次封装,实现请求的标准化和统一化。

utils/request_util.py中,封装请求工具类:

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from config.global_config import GlobalConfig

class RequestUtil:
def __init__(self):
self.session = requests.Session()
# 配置重试机制
retry = Retry(
total=GlobalConfig.RETRY_TIMES,
backoff_factor=GlobalConfig.BACKOFF_FACTOR,
status_forcelist=[500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry)
self.session.mount('http://', adapter)
self.session.mount('https://', adapter)
# 设置默认超时时间
self.timeout = GlobalConfig.TIMEOUT

def send_request(self, method, url, **kwargs):
"""
发送HTTP请求
:param method: 请求方法,如GET、POST、PUT、DELETE等
:param url: 请求地址
:param kwargs: 其他请求参数,如headers、params、json、data等
:return: 响应对象
"""
try:
response = self.session.request(
method=method.upper(),
url=url,
timeout=self.timeout,
**kwargs
)
response.raise_for_status() # 抛出HTTP错误异常
return response
except requests.exceptions.RequestException as e:
print(f"请求发送失败: {str(e)}")
raise

上述代码中,通过Session对象建立持久连接,提高请求效率;配置重试机制,当遇到500、502等服务器错误时,自动重试指定次数;设置默认超时时间,避免请求长时间阻塞;捕获并处理请求过程中的异常,确保测试用例的稳定性。

2.2 断言工具类封装

Pytest支持使用Python原生的assert语句进行断言,但在实际项目中,原生断言的错误信息不够直观,不利于问题定位。因此,需要封装自定义的断言方法,提供更详细的错误提示。

utils/assert_util.py中,封装断言工具类:

class AssertUtil:
@staticmethod
def assert_equal(actual, expected, msg=None):
"""
断言两个值相等
:param actual: 实际结果
:param expected: 预期结果
:param msg: 断言失败时的提示信息
"""
try:
assert actual == expected, msg or f"实际结果: {actual} 与预期结果: {expected} 不相等"
except AssertionError as e:
print(f"断言失败: {str(e)}")
raise

@staticmethod
def assert_in(actual, expected, msg=None):
"""
断言预期结果包含在实际结果中
:param actual: 实际结果
:param expected: 预期包含的内容
:param msg: 断言失败时的提示信息
"""
try:
assert expected in actual, msg or f"预期内容: {expected} 未在实际结果: {actual} 中找到"
except AssertionError as e:
print(f"断言失败: {str(e)}")
raise

@staticmethod
def assert_status_code(response, expected_code):
"""
断言响应状态码
:param response: 响应对象
:param expected_code: 预期状态码
"""
try:
assert response.status_code == expected_code, \
f"响应状态码错误,实际状态码: {response.status_code},预期状态码: {expected_code}"
except AssertionError as e:
print(f"断言失败: {str(e)}")
raise

通过封装这些断言方法,在测试用例中调用时,能够得到更清晰的错误提示,提高问题排查效率。

2.3 配置文件管理

config/global_config.py中,定义全局配置变量,实现不同环境的统一管理:

class GlobalConfig:
# 环境配置
ENV = "test" # 测试环境:test、pre、prod
BASE_URLS = {
"test": "https://test.example.com/api",
"pre": "https://pre.example.com/api",
"prod": "https://example.com/api"
}
BASE_URL = BASE_URLS[ENV]

# 请求配置
TIMEOUT = 10 # 请求超时时间,单位:秒
RETRY_TIMES = 3 # 请求重试次数
BACKOFF_FACTOR = 1 # 重试间隔时间系数

# 日志配置
LOG_LEVEL = "INFO"
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

通过修改ENV变量,即可快速切换测试环境,无需在多个测试文件中逐一修改接口地址,大大提高了环境切换的效率。

三、测试用例编写

3.1 测试用例设计原则

编写测试用例时,应遵循以下原则:

  • 单一职责原则:每个测试用例只关注一个功能点,确保用例的独立性和可维护性。

  • 覆盖全面原则:覆盖正常场景、异常场景和边界场景,确保接口的稳定性和健壮性。

  • 数据驱动原则:将测试数据与测试代码分离,通过YAML、JSON等文件管理测试数据,提高用例的复用性和灵活性。

3.2 测试用例编写示例

以用户模块的登录接口为例,在testcases/test_user_api.py中编写测试用例:

import pytest
from utils.request_util import RequestUtil
from utils.assert_util import AssertUtil
from config.global_config import GlobalConfig
import yaml

# 加载测试数据
with open("resources/user_data.yaml", "r", encoding="utf-8") as f:
user_data = yaml.safe_load(f)

class TestUserAPI:
def setup_class(self):
"""测试类初始化方法,在所有测试方法执行前执行一次"""
self.request_util = RequestUtil()
self.assert_util = AssertUtil()
self.login_url = f"{GlobalConfig.BASE_URL}/user/login"

@pytest.mark.parametrize("case", user_data["login_success_cases"])
def test_login_success(self, case):
"""登录接口正常场景测试"""
# 发送请求
response = self.request_util.send_request(
method="POST",
url=self.login_url,
json=case["request_data"]
)
# 断言响应状态码
self.assert_util.assert_status_code(response, case["expected_code"])
# 断言响应内容
response_json = response.json()
self.assert_util.assert_in("token", response_json, "登录成功未返回token")
self.assert_util.assert_equal(response_json["code"], case["expected_code"], "响应码与预期不符")

@pytest.mark.parametrize("case", user_data["login_fail_cases"])
def test_login_fail(self, case):
"""登录接口异常场景测试"""
# 发送请求
response = self.request_util.send_request(
method="POST",
url=self.login_url,
json=case["request_data"]
)
# 断言响应状态码
self.assert_util.assert_status_code(response, case["expected_code"])
# 断言响应内容
response_json = response.json()
self.assert_util.assert_equal(response_json["code"], case["expected_code"], "响应码与预期不符")
self.assert_util.assert_equal(response_json["msg"], case["expected_msg"], "响应提示信息与预期不符")

测试数据文件resources/user_data.yaml内容如下:

login_success_cases:
- case_name: 正确用户名和密码登录
request_data:
username: "test_user"
password: "test123"
expected_code: 200
expected_msg: "登录成功"

login_fail_cases:
- case_name: 用户名不存在
request_data:
username: "invalid_user"
password: "test123"
expected_code: 400
expected_msg: "用户名不存在"
- case_name: 密码错误
request_data:
username: "test_user"
password: "wrong_password"
expected_code: 400
expected_msg: "密码错误"

通过@pytest.mark.parametrize装饰器实现数据驱动测试,将测试数据与测试代码分离,提高了用例的复用性和灵活性。每个测试用例只关注一个场景,确保了用例的独立性和可维护性。

四、测试执行与报告生成

4.1 测试执行

在项目根目录下,创建run.py文件,作为测试执行的入口:

import pytest
import os

if __name__ == "__main__":
# 配置测试报告路径
html_report_path = os.path.join("reports", "html", "report.html")
allure_report_path = os.path.join("reports", "allure")

# 构建Pytest命令行参数
args = [
"testcases/",
"-v", # 显示详细的测试结果
"--html=" + html_report_path, # 生成HTML报告
"--alluredir=" + allure_report_path, # 生成Allure报告原始数据
"--clean-alluredir" # 清空之前的Allure报告数据
]

# 执行测试
pytest.main(args)

运行run.py文件,即可执行所有测试用例。执行过程中,Pytest会自动发现testcases目录下的测试用例,并按照配置生成测试报告。

4.2 测试报告查看

  • HTML报告:测试执行完成后,打开reports/html/report.html文件,即可查看HTML格式的测试报告。报告中包含了测试用例的执行结果、执行时间、失败用例的详细信息等,便于快速查看测试结果。

  • Allure报告:生成Allure报告需要先安装Allure命令行工具,安装完成后,在项目根目录下执行以下命令:

    allure serve reports/allure

    命令执行后,会启动一个本地服务器,并在浏览器中打开Allure报告。Allure报告提供了更丰富的可视化信息,如测试用例的执行趋势、失败用例的堆栈信息、测试覆盖率等,便于深入分析测试结果。

五、框架扩展与优化

5.1 日志功能集成

在测试执行过程中,日志信息对于问题排查至关重要。可以使用Python的logging模块,封装日志工具类,实现日志的记录和管理。在utils/log_util.py中,封装日志工具类:

import logging
from config.global_config import GlobalConfig
import os

class LogUtil:
def __init__(self, logger_name):
self.logger = logging.getLogger(logger_name)
self.logger.setLevel(GlobalConfig.LOG_LEVEL)

# 避免重复添加处理器
if not self.logger.handlers:
# 创建日志文件目录
if not os.path.exists("logs"):
os.makedirs("logs")

# 创建文件处理器
file_handler = logging.FileHandler(
os.path.join("logs", "test.log"),
encoding="utf-8"
)
file_handler.setLevel(GlobalConfig.LOG_LEVEL)

# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(GlobalConfig.LOG_LEVEL)

# 创建日志格式器
formatter = logging.Formatter(GlobalConfig.LOG_FORMAT)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 添加处理器到日志器
self.logger.addHandler(file_handler)
self.logger.addHandler(console_handler)

def get_logger(self):
return self.logger

在请求工具类和测试用例中,集成日志功能,记录请求的发送、响应的返回以及断言的结果等信息,便于问题排查。

5.2 持续集成集成

将接口自动化测试框架与Jenkins、GitLab CI等持续集成工具集成,实现代码提交后自动触发测试,提高测试效率。以Jenkins为例,配置步骤如下:

  1. 在Jenkins中创建一个新的任务,选择“自由风格项目”。

  2. 在“源码管理”中配置Git仓库地址,拉取测试代码。

  3. 在“构建环境”中,配置Python虚拟环境,激活虚拟环境并安装依赖。

  4. 在“构建”步骤中,执行python run.py命令,启动测试。

  5. 在“构建后操作”中,配置Allure报告的展示,便于查看测试结果。

通过持续集成集成,实现了测试的自动化执行,确保了代码的质量和稳定性。

六、总结

基于Python+Requests+Pytest搭建的接口自动化测试框架,具有简洁高效、可扩展性强、易于维护等优点。通过合理的目录结构设计、核心模块的封装、测试用例的规范编写以及测试报告的生成,能够有效提升接口自动化测试的效率和质量。同时,通过框架的扩展与优化,如日志功能集成、持续集成集成等,进一步增强了框架的实用性和稳定性。在实际项目中,应根据项目的具体需求,对框架进行适当的调整和优化,以满足项目的测试需求。

更多推荐