Python接口自动化测试入门:Requests+Pytest+Allure实战项目详解

📅 2026/7/3 3:19:11
Python接口自动化测试入门:Requests+Pytest+Allure实战项目详解
1. 项目概述为什么需要一个“简易”的接口自动化实战项目在软件测试领域接口自动化测试的重要性已经无需赘言。它不仅是保障软件质量、提升回归测试效率的利器更是测试工程师向更高阶技术岗位如测试开发、质量效能工程师发展的核心技能之一。然而对于许多初学者或希望从手工测试转型的同行来说最大的障碍往往不是理解概念而是“如何开始”。市面上的框架如PytestRequests、TestNGHttpClient功能强大但学习曲线陡峭配置复杂一个简单的“Hello World”可能就需要处理环境依赖、目录结构、配置文件等一堆问题很容易让人在第一步就望而却步。这正是我分享这个“简易项目”的初衷。它不是一个生产级的、大而全的框架而是一个精心裁剪过的、开箱即用的实战练习沙盒。它的目标非常明确让你在最短的时间内绕过繁琐的初始配置直接接触到接口自动化测试最核心的环节——发送请求、验证响应、组织用例、生成报告。通过完成这个项目你将能清晰地建立起接口自动化测试的基本工作流和核心概念为后续学习更复杂的框架和设计模式打下坚实的基础。无论你是刚入行的测试新人还是想巩固基础的中级工程师这个项目都能提供一个绝佳的动手环境。2. 项目核心设计与思路拆解2.1 技术栈选型为什么是Python Requests Pytest Allure这个项目的技术栈组合是经过深思熟虑的旨在平衡易用性、功能性和学习价值。Python作为入门自动化测试的首选语言其语法简洁、库生态丰富能让你更专注于测试逻辑本身而非语言细节。对于测试领域Python的社区支持和相关资料也是最全面的。Requests库这是Python中处理HTTP请求的“事实标准”。它提供了极其人性化的API发送一个GET或POST请求几乎就像写一句口语。相比于Python内置的urllibRequests极大地降低了HTTP交互的复杂度是学习接口测试的完美起点。Pytest测试框架Pytest是当前Python测试界的绝对主流。它比自带的unittest更简洁灵活例如不需要写类直接用函数就可以作为测试用例夹具fixture功能强大插件生态丰富。选择Pytest意味着你学的是行业主流实践未来过渡到企业级项目毫无压力。Allure测试报告测试执行完了结果怎么看Allure提供了非常美观、交互性强的HTML报告能清晰展示用例通过率、执行时长、失败日志甚至支持附加截图、文本等附件。一个专业的报告能让你的工作成果可视化无论是用于团队分享还是个人复盘都极具价值。注意这个组合避开了诸如unittest、nose等相对陈旧或小众的框架也暂不引入数据驱动如pytest-parametrize的复杂用法、关键字驱动等高级概念目的是确保核心路径清晰。先学会走再学跑。2.2 项目结构设计清晰即正义一个清晰的项目结构是维护性和可读性的保障。我们这个简易项目的结构如下api_auto_test_demo/ ├── common/ # 公共模块 │ ├── __init__.py │ ├── logger.py # 日志记录模块 │ └── request_client.py # 封装的HTTP请求客户端 ├── config/ # 配置模块 │ ├── __init__.py │ └── settings.py # 全局配置如基础URL、超时时间 ├── test_cases/ # 测试用例目录 │ ├── __init__.py │ ├── test_demo_api.py # 具体的测试用例文件 │ └── conftest.py # Pytest的本地配置文件可放夹具 ├── test_data/ # 测试数据目录如JSON文件 │ └── user_data.json ├── reports/ # 测试报告输出目录由Allure生成 ├── logs/ # 日志文件输出目录 ├── requirements.txt # Python依赖包列表 ├── pytest.ini # Pytest框架配置文件 └── README.md # 项目说明文档设计思路解析common/: 封装重复代码。比如我们将对Requests的调用进行二次封装加入日志记录、通用断言、异常处理等这样在每个测试用例中只需关注业务参数和断言逻辑。config/: 集中管理配置。将基础URL、数据库连接串等可变参数放在配置文件中避免硬编码方便不同环境测试、预生产切换。test_cases/: 用例按模块或功能划分文件。conftest.py是Pytest的特有文件用于存放会被多个用例文件共享的fixture例如初始化一个登录态的token。test_data/: 提倡测试数据与代码分离。简单的数据可以放在JSON或YAML文件中复杂场景未来可以连接数据库。reports/ logs/: 输出目录与源码分离保持项目根目录整洁。3. 核心细节解析与实操要点3.1 请求客户端的封装不止是发送请求直接在每个用例里写requests.get(url)当然可以但这会产生大量重复代码且不利于统一管理请求头、超时、重试策略等。因此封装一个通用的请求客户端是第一步。在common/request_client.py中我们会创建一个RequestClient类import requests import json from common.logger import get_logger class RequestClient: def __init__(self, base_urlNone): self.session requests.Session() # 使用Session保持会话如cookie self.base_url base_url self.logger get_logger(__name__) # 可以在这里设置默认请求头如Content-Type self.default_headers { Content-Type: application/json; charsetUTF-8, } def request(self, method, endpoint, **kwargs): 发送请求的核心方法 url f{self.base_url}{endpoint} if self.base_url else endpoint # 处理请求数据如果传入的是dict自动转换为JSON字符串 if json in kwargs and isinstance(kwargs[json], dict): kwargs[data] json.dumps(kwargs.pop(json)) kwargs.setdefault(headers, {}).update({Content-Type: application/json}) # 合并默认请求头 headers kwargs.pop(headers, {}) final_headers {**self.default_headers, **headers} kwargs[headers] final_headers self.logger.info(f请求开始: {method} {url}) self.logger.debug(f请求参数: {kwargs}) try: response self.session.request(method, url, **kwargs) self.logger.info(f请求结束: 状态码{response.status_code}) self.logger.debug(f响应内容: {response.text[:500]}...) # 日志只记录前500字符 return response except requests.exceptions.RequestException as e: self.logger.error(f请求发生异常: {e}) raise # 定义便捷方法 def get(self, endpoint, paramsNone, **kwargs): return self.request(GET, endpoint, paramsparams, **kwargs) def post(self, endpoint, dataNone, jsonNone, **kwargs): return self.request(POST, endpoint, datadata, jsonjson, **kwargs) # 类似地可以封装put, delete等方法封装的价值日志集成每个请求的入参、出参、异常都被自动记录调试时一目了然。会话保持使用requests.Session()可以自动处理Cookie模拟浏览器行为对于需要登录的接口测试至关重要。统一处理可以在这里统一添加鉴权头如Token、设置代理、定义重试逻辑等。简化调用在用例中只需要client.post(/login, json{user:admin})更加简洁。3.2 测试用例编写Pytest的优雅实践有了客户端编写测试用例就变得非常直观。我们以测试一个简单的用户查询接口为例。在test_cases/test_demo_api.py中import pytest from common.request_client import RequestClient # 假设我们在config/settings.py中定义了基础URL from config import settings class TestUserAPI: 用户相关接口测试类 pytest.fixture(scopeclass) def client(self): 创建一个用于整个测试类的客户端实例 client RequestClient(base_urlsettings.BASE_URL) yield client # 测试类结束后可以做一些清理工作比如关闭sessionrequests.Session会自动处理 pytest.fixture def auth_client(self, client): 一个已经完成登录认证的客户端夹具 # 先调用登录接口获取token login_resp client.post(/api/login, json{username: testuser, password: testpass}) token login_resp.json()[data][token] # 将token设置到客户端的默认头中 client.default_headers[Authorization] fBearer {token} return client def test_get_user_list_without_auth(self, client): 测试未授权情况下获取用户列表应失败 response client.get(/api/users) # 断言状态码为401未授权 assert response.status_code 401 # 断言响应体中包含错误信息 assert Unauthorized in response.text def test_get_user_list_with_auth(self, auth_client): 测试授权后获取用户列表应成功 response auth_client.get(/api/users) # 断言状态码为200 assert response.status_code 200 # 断言响应体是JSON格式 json_data response.json() assert isinstance(json_data, dict) # 断言返回的数据中包含用户列表字段 assert users in json_data[data] # 断言用户列表非空这里假设至少有一个用户 assert len(json_data[data][users]) 0 def test_get_user_by_id(self, auth_client): 测试根据ID获取特定用户信息 # 假设我们先获取列表然后取第一个用户的ID进行查询 list_resp auth_client.get(/api/users) first_user_id list_resp.json()[data][users][0][id] response auth_client.get(f/api/users/{first_user_id}) assert response.status_code 200 user_info response.json()[data] # 断言获取到的用户ID与查询的ID一致 assert user_info[id] first_user_id # 断言必要的字段存在且类型正确 assert name in user_info and isinstance(user_info[name], str) assert email in user_info and in user_info[email]要点解析使用fixturepytest.fixture是Pytest的精髓。client夹具为整个测试类提供统一的请求客户端。auth_client夹具依赖client并在此基础上完成了登录操作返回一个已认证的客户端。这实现了代码的复用和测试前置条件的封装。断言的艺术断言不要只检查状态码。要检查响应的数据结构、关键字段的值和类型、业务逻辑的正确性如未授权访问应失败。使用Python内置的assert语句即可Pytest会提供丰富的失败信息。用例独立性理想情况下每个测试用例应该是独立的。但这里的auth_client夹具确保了每个需要认证的用例都能获得一个干净的、已登录的会话。test_get_user_by_id用例中甚至包含了“先获取列表再查询详情”的小流程模拟了真实用户操作。3.3 配置文件与测试数据管理将易变的部分配置化。config/settings.pyimport os # 通过环境变量读取配置便于CI/CD集成默认使用测试环境 ENV os.getenv(TEST_ENV, test) configs { test: { BASE_URL: http://httpbin.org, # 使用一个免费的在线测试接口服务 DB_CONNECTION: ..., # 数据库连接串如果需要 TIMEOUT: 10, }, staging: { BASE_URL: http://staging-api.example.com, DB_CONNECTION: ..., TIMEOUT: 15, } } settings configs[ENV]在test_data/user_data.json中管理测试数据{ valid_users: [ {username: test1, password: pass123, expected_role: user}, {username: admin1, password: admin123, expected_role: admin} ], invalid_users: [ {username: , password: pass123, expected_error: 用户名不能为空}, {username: test1, password: wrong, expected_error: 密码错误} ] }在用例中读取import json with open(test_data/user_data.json, r, encodingutf-8) as f: user_data json.load(f) pytest.mark.parametrize(user, user_data[valid_users]) def test_login_with_valid_data(client, user): response client.post(/api/login, jsonuser) assert response.status_code 200 assert response.json()[data][role] user[expected_role]4. 完整实操过程从零到报告生成4.1 环境准备与依赖安装安装Python确保系统已安装Python 3.7或以上版本。在命令行输入python --version检查。创建项目目录按照上文所述的结构手动创建文件夹和文件。安装依赖在项目根目录创建requirements.txt文件内容如下requests2.28.0 pytest7.0.0 pytest-html3.2.0 allure-pytest2.9.45 PyYAML6.0 # 如果使用yaml管理测试数据在命令行进入项目目录执行安装pip install -r requirements.txt安装Allure命令行工具Allure报告需要命令行工具支持。Mac:brew install allureWindows: 可从 GitHub Releases 下载zip包解压后将bin目录加入系统PATH环境变量。安装后在命令行运行allure --version验证。4.2 编写核心代码并运行测试填充代码将前面章节提供的request_client.py,settings.py,test_demo_api.py等文件的代码内容分别写入对应位置。配置Pytest在项目根目录创建pytest.ini文件这是一个配置文件可以简化命令行参数。[pytest] # 指定测试文件的位置和模式 testpaths test_cases # 自动发现以 test_ 开头或 _test 结尾的文件/类/函数 python_files test_*.py python_classes Test* python_functions test_* # 添加命令行默认参数 addopts -v # 详细输出 --tbshort # 发生错误时打印简短的traceback信息 --strict-markers # 严格检查marker # 定义一些自定义标记用于分类运行用例 markers smoke: 冒烟测试 regression: 回归测试首次运行测试在项目根目录打开终端执行pytest如果一切正常你会看到Pytest收集并运行了你写的测试用例并输出简单的点状结果.表示通过F表示失败。4.3 生成并查看Allure测试报告仅通过控制台输出看结果不够直观我们使用Allure生成HTML报告。运行测试并生成Allure结果数据Pytest运行时需要指定--alluredir参数来告诉它把结果数据一堆JSON文件存到哪里。pytest --alluredir./reports/allure-results生成HTML报告使用Allure命令行工具将上一步生成的结果数据转换为一个可交互的HTML网站。allure generate ./reports/allure-results -o ./reports/allure-report --cleangenerate: 生成命令。./reports/allure-results: 上一步生成的结果数据目录。-o ./reports/allure-report: 指定HTML报告的输出目录。--clean: 如果输出目录已存在则先清理。打开报告生成报告后你可以直接打开./reports/allure-report/index.html文件用浏览器打开或者使用命令启动一个本地服务预览allure open ./reports/allure-report报告会展示概览总览、通过率、趋势图、用例列表、图表分析等点击每个用例可以看到详细的请求、响应、日志和断言信息对于排查失败用例非常有帮助。4.4 集成日志系统为了更好的追溯问题我们需要一个日志系统。在common/logger.py中import logging import os from datetime import datetime def get_logger(name, log_levellogging.INFO): 获取一个配置好的logger实例 logger logging.getLogger(name) if logger.handlers: # 防止重复添加handler return logger logger.setLevel(log_level) # 定义日志格式 formatter logging.Formatter( %(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s ) # 控制台处理器 console_handler logging.StreamHandler() console_handler.setLevel(log_level) console_handler.setFormatter(formatter) logger.addHandler(console_handler) # 文件处理器 - 按日期生成日志文件 log_dir logs os.makedirs(log_dir, exist_okTrue) log_file os.path.join(log_dir, ftest_{datetime.now().strftime(%Y%m%d)}.log) file_handler logging.FileHandler(log_file, encodingutf-8) file_handler.setLevel(logging.DEBUG) # 文件里记录更详细的DEBUG信息 file_handler.setFormatter(formatter) logger.addHandler(file_handler) return logger在request_client.py和你的测试用例中通过get_logger(__name__)来获取logger实例然后使用logger.info(),logger.error()等方法记录日志。5. 常见问题与排查技巧实录在实际操作中你肯定会遇到各种问题。下面是我在多年实践中总结的一些典型场景和解决方法。5.1 环境与依赖问题问题1运行pytest命令提示“找不到模块” (ModuleNotFoundError)原因Python解释器找不到你的项目模块。通常是因为运行命令的目录不对或者没有正确设置Python路径。解决确保在项目根目录即包含pytest.ini的目录下运行命令。如果项目结构复杂可以尝试设置环境变量PYTHONPATH。在项目根目录执行export PYTHONPATH$(pwd)(Linux/Mac) 或set PYTHONPATH%cd%(Windows)。更推荐的方式是使用虚拟环境venv并在该环境下安装所有依赖确保环境隔离。问题2Allure报告打开后是空白页或样式丢失原因浏览器因为安全策略如CORS或本地文件限制阻止加载CSS/JS文件。解决最佳实践总是使用allure open命令来打开报告它会启动一个本地HTTP服务避免文件协议的限制。如果必须直接打开HTML文件可以尝试Chrome: 关闭所有Chrome窗口然后用命令行启动chrome --allow-file-access-from-files注意安全风险。或者将整个allure-report文件夹部署到一个简单的HTTP服务器如python -m http.server中通过http://localhost:8000访问。5.2 测试用例编写与执行问题问题3测试用例之间相互影响比如A用例登录后B用例直接继承了A的登录状态导致测试逻辑错误原因如果使用requests.Session()且未正确重置或者使用了全局变量/类变量共享了状态就可能发生这种情况。解决正确使用Fixture作用域对于需要独立状态的客户端将pytest.fixture的scope参数设为function默认值这样每个测试函数都会获得一个全新的fixture实例。在Fixture中清理状态在Fixture的yield语句之后编写清理代码。例如在auth_client夹具的yield之后可以添加client.default_headers.pop(Authorization, None)来移除Token。设计用例为无状态尽量让每个用例自己完成所需的前置条件如登录而不是依赖其他用例的执行结果。问题4断言失败时Pytest输出的信息不够详细不知道具体是哪个字段不对解决使用Pytest的-v详细和-s输出打印信息参数运行pytest -v -s。在断言前先将响应内容打印出来print(response.json())。但更优雅的方式是使用日志记录。使用更智能的断言库虽然Python自带的assert够用但像pytest-assume支持多重断言一个失败后继续执行后续断言或assertpy提供更流畅的断言语法可以提升体验。例如安装pytest-assume后from pytest import assume def test_complex_response(response): json_data response.json() assume(json_data[status] success) assume(len(json_data[data][items]) 0) assume(id in json_data[data][items][0]) # 即使第一个assume失败后面的依然会执行方便一次看到所有问题5.3 请求与响应处理问题问题5接口返回中文乱码原因服务器返回的编码和Requests解析的编码不一致。解决首先检查响应头print(response.encoding)。Requests会根据HTTP头猜测编码。如果猜测错误可以手动指定response.encoding utf-8或response.encoding gbk。对于响应内容也可以直接使用response.content.decode(utf-8)来获取字符串。问题6POST请求发送JSON数据时服务器提示参数错误解决检查请求头确保Content-Type: application/json已正确设置。我们的RequestClient封装已经处理了这一点。检查数据格式使用json.dumps()确保Python字典被正确转换为JSON字符串。同样我们的封装也处理了。使用工具对比先用Postman或浏览器开发者工具发送一个成功的请求抓取到原始的HTTP请求数据。然后在你代码中使用logger.debug将准备发送的数据和请求头完整打印出来与成功请求进行逐字对比。差异往往在于空格、引号、日期格式等细节。如果接口接收的是form-data或x-www-form-urlencoded则不能使用json参数而应使用data参数传入一个字典。问题7如何处理需要上传文件的接口解决Requests库对文件上传支持很好。files {file: (report.pdf, open(/path/to/report.pdf, rb), application/pdf)} # 如果有其他普通参数 data {description: 月度报告} response client.post(/api/upload, filesfiles, datadata)注意文件句柄使用后最好在适当位置关闭或者使用with open(...) as f:上下文管理器。5.4 报告与日志问题问题8Allure报告中没有显示我打的日志原因Pytest默认不会将print语句或Python的logging输出捕获到Allure结果中。解决需要安装并配置allure-pytest插件它已经处理了与Pythonlogging标准库的集成。确保你的日志是通过logging.getLogger()获得的logger记录的而不是简单的print。在pytest.ini中可以添加--capturesys默认或-s来确保标准输出被捕获但-s可能会让控制台输出变得混乱。更可靠的方式是使用Allure特有的附件功能添加日志但这更复杂。对于初学者确保使用logging模块并正确配置handler输出到文件和控制台就能在Allure的“用例详情”页的“Logs”标签页看到输出。问题9如何让失败的用例自动截图解决这在Web UI自动化中很常见在纯接口测试中较少。但如果你的接口返回了错误信息的图片或者你想附加其他证据可以使用Allure的附件功能。首先在用例中捕获你想要附加的内容如错误响应体、某个关键变量值然后使用allure.attach将其添加到报告中。import allure def test_something(client): response client.get(/api/some-endpoint) if response.status_code ! 200: # 将错误的响应体作为文本附件添加到报告 allure.attach(bodyresponse.text, nameError Response, attachment_typeallure.attachment_type.TEXT) # 如果你有图片的二进制数据 # allure.attach(bodyimage_data, nameScreenshot, attachment_typeallure.attachment_type.PNG) assert False, f请求失败状态码{response.status_code}附件会在Allure报告的该用例详情中显示出来。5.5 项目组织与进阶思考问题10随着用例增多如何高效组织和管理解决目录分层在test_cases下按业务模块创建子目录如test_cases/user_management/,test_cases/order_processing/。使用标记Mark在pytest.ini中定义好标记后可以在用例上用pytest.mark.smoke装饰。然后可以只运行冒烟测试pytest -m smoke。数据驱动对于大量相似用例如测试不同登录账号使用pytest.mark.parametrize将测试数据与测试逻辑分离。公共夹具提取将多个模块都需要用到的夹具如全局初始化、数据库连接放到test_cases/conftest.py或项目根目录的conftest.py中Pytest会自动发现。这个简易项目就像一副骨架你已经掌握了接口自动化测试的核心肌肉和关节。接下来你可以根据实际需求为其增添“血肉”例如集成CI/CDJenkins/GitLab CI、连接数据库进行数据验证、封装更复杂的断言工具、引入API契约测试如使用Pact甚至是搭建一个简单的测试平台。记住自动化测试不是一蹴而就的而是一个持续迭代和优化的过程。从这个清晰、可运行的起点出发每一步扩展都会让你对质量保障体系有更深刻的理解。