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

📅 2026/7/3 3:37:34 👁️ 阅读次数 📝 编程学习
Python接口自动化测试入门:Requests+Pytest+Allure实战项目详解

1. 项目概述:为什么需要一个“简易”的接口自动化实战项目?

在软件测试领域,接口自动化测试的重要性已经无需赘言。它不仅是保障软件质量、提升回归测试效率的利器,更是测试工程师向更高阶技术岗位(如测试开发、质量效能工程师)发展的核心技能之一。然而,对于许多初学者或希望从手工测试转型的同行来说,最大的障碍往往不是理解概念,而是“如何开始”。市面上的框架(如Pytest+Requests、TestNG+HttpClient)功能强大,但学习曲线陡峭,配置复杂,一个简单的“Hello World”可能就需要处理环境依赖、目录结构、配置文件等一堆问题,很容易让人在第一步就望而却步。

这正是我分享这个“简易项目”的初衷。它不是一个生产级的、大而全的框架,而是一个精心裁剪过的、开箱即用的实战练习沙盒。它的目标非常明确:让你在最短的时间内,绕过繁琐的初始配置,直接接触到接口自动化测试最核心的环节——发送请求、验证响应、组织用例、生成报告。通过完成这个项目,你将能清晰地建立起接口自动化测试的基本工作流和核心概念,为后续学习更复杂的框架和设计模式打下坚实的基础。无论你是刚入行的测试新人,还是想巩固基础的中级工程师,这个项目都能提供一个绝佳的动手环境。

2. 项目核心设计与思路拆解

2.1 技术栈选型:为什么是Python + Requests + Pytest + Allure?

这个项目的技术栈组合是经过深思熟虑的,旨在平衡易用性、功能性和学习价值

  1. Python:作为入门自动化测试的首选语言,其语法简洁、库生态丰富,能让你更专注于测试逻辑本身,而非语言细节。对于测试领域,Python的社区支持和相关资料也是最全面的。
  2. Requests库:这是Python中处理HTTP请求的“事实标准”。它提供了极其人性化的API,发送一个GET或POST请求几乎就像写一句口语。相比于Python内置的urllib,Requests极大地降低了HTTP交互的复杂度,是学习接口测试的完美起点。
  3. Pytest测试框架:Pytest是当前Python测试界的绝对主流。它比自带的unittest更简洁灵活(例如,不需要写类,直接用函数就可以作为测试用例),夹具(fixture)功能强大,插件生态丰富。选择Pytest,意味着你学的是行业主流实践,未来过渡到企业级项目毫无压力。
  4. Allure测试报告:测试执行完了,结果怎么看?Allure提供了非常美观、交互性强的HTML报告,能清晰展示用例通过率、执行时长、失败日志,甚至支持附加截图、文本等附件。一个专业的报告能让你的工作成果可视化,无论是用于团队分享还是个人复盘,都极具价值。

注意:这个组合避开了诸如unittestnose等相对陈旧或小众的框架,也暂不引入数据驱动(如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_url=None): 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; charset=UTF-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, params=None, **kwargs): return self.request('GET', endpoint, params=params, **kwargs) def post(self, endpoint, data=None, json=None, **kwargs): return self.request('POST', endpoint, data=data, json=json, **kwargs) # 类似地,可以封装put, delete等方法

封装的价值

  1. 日志集成:每个请求的入参、出参、异常都被自动记录,调试时一目了然。
  2. 会话保持:使用requests.Session(),可以自动处理Cookie,模拟浏览器行为,对于需要登录的接口测试至关重要。
  3. 统一处理:可以在这里统一添加鉴权头(如Token)、设置代理、定义重试逻辑等。
  4. 简化调用:在用例中,只需要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(scope="class") def client(self): """创建一个用于整个测试类的客户端实例""" client = RequestClient(base_url=settings.BASE_URL) yield client # 测试类结束后可以做一些清理工作,比如关闭session(requests.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'] = f'Bearer {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']

要点解析

  • 使用fixture@pytest.fixture是Pytest的精髓。client夹具为整个测试类提供统一的请求客户端。auth_client夹具依赖client,并在此基础上完成了登录操作,返回一个已认证的客户端。这实现了代码的复用和测试前置条件的封装。
  • 断言的艺术:断言不要只检查状态码。要检查响应的数据结构、关键字段的值和类型、业务逻辑的正确性(如未授权访问应失败)。使用Python内置的assert语句即可,Pytest会提供丰富的失败信息。
  • 用例独立性:理想情况下,每个测试用例应该是独立的。但这里的auth_client夹具确保了每个需要认证的用例都能获得一个干净的、已登录的会话。test_get_user_by_id用例中甚至包含了“先获取列表,再查询详情”的小流程,模拟了真实用户操作。

3.3 配置文件与测试数据管理

将易变的部分配置化。config/settings.py

import 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', encoding='utf-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', json=user) assert response.status_code == 200 assert response.json()['data']['role'] == user['expected_role']

4. 完整实操过程:从零到报告生成

4.1 环境准备与依赖安装

  1. 安装Python:确保系统已安装Python 3.7或以上版本。在命令行输入python --version检查。
  2. 创建项目目录:按照上文所述的结构,手动创建文件夹和文件。
  3. 安装依赖:在项目根目录创建requirements.txt文件,内容如下:
    requests>=2.28.0 pytest>=7.0.0 pytest-html>=3.2.0 allure-pytest>=2.9.45 PyYAML>=6.0 # 如果使用yaml管理测试数据
    在命令行进入项目目录,执行安装:
    pip install -r requirements.txt
  4. 安装Allure命令行工具:Allure报告需要命令行工具支持。
    • Mac:brew install allure
    • Windows: 可从 GitHub Releases 下载zip包,解压后将bin目录加入系统PATH环境变量。
    • 安装后,在命令行运行allure --version验证。

4.2 编写核心代码并运行测试

  1. 填充代码:将前面章节提供的request_client.py,settings.py,test_demo_api.py等文件的代码内容分别写入对应位置。
  2. 配置Pytest:在项目根目录创建pytest.ini文件,这是一个配置文件,可以简化命令行参数。
    [pytest] # 指定测试文件的位置和模式 testpaths = test_cases # 自动发现以 test_ 开头或 _test 结尾的文件/类/函数 python_files = test_*.py python_classes = Test* python_functions = test_* # 添加命令行默认参数 addopts = -v # 详细输出 --tb=short # 发生错误时,打印简短的traceback信息 --strict-markers # 严格检查marker # 定义一些自定义标记,用于分类运行用例 markers = smoke: 冒烟测试 regression: 回归测试
  3. 首次运行测试:在项目根目录打开终端,执行:
    pytest
    如果一切正常,你会看到Pytest收集并运行了你写的测试用例,并输出简单的点状结果(.表示通过,F表示失败)。

4.3 生成并查看Allure测试报告

仅通过控制台输出看结果不够直观,我们使用Allure生成HTML报告。

  1. 运行测试并生成Allure结果数据:Pytest运行时需要指定--alluredir参数来告诉它把结果数据(一堆JSON文件)存到哪里。
    pytest --alluredir=./reports/allure-results
  2. 生成HTML报告:使用Allure命令行工具,将上一步生成的结果数据转换为一个可交互的HTML网站。
    allure generate ./reports/allure-results -o ./reports/allure-report --clean
    • generate: 生成命令。
    • ./reports/allure-results: 上一步生成的结果数据目录。
    • -o ./reports/allure-report: 指定HTML报告的输出目录。
    • --clean: 如果输出目录已存在,则先清理。
  3. 打开报告:生成报告后,你可以直接打开./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_level=logging.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_ok=True) log_file = os.path.join(log_dir, f"test_{datetime.now().strftime('%Y%m%d')}.log") file_handler = logging.FileHandler(log_file, encoding='utf-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路径。
  • 解决
    1. 确保在项目根目录(即包含pytest.ini的目录)下运行命令。
    2. 如果项目结构复杂,可以尝试设置环境变量PYTHONPATH。在项目根目录执行:export PYTHONPATH=$(pwd)(Linux/Mac) 或set PYTHONPATH=%cd%(Windows)。
    3. 更推荐的方式是使用虚拟环境(venv)并在该环境下安装所有依赖,确保环境隔离。

问题2:Allure报告打开后是空白页或样式丢失

  • 原因:浏览器因为安全策略(如CORS或本地文件限制)阻止加载CSS/JS文件。
  • 解决
    1. 最佳实践:总是使用allure open命令来打开报告,它会启动一个本地HTTP服务,避免文件协议的限制。
    2. 如果必须直接打开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()且未正确重置,或者使用了全局变量/类变量共享了状态,就可能发生这种情况。
  • 解决
    1. 正确使用Fixture作用域:对于需要独立状态的客户端,将@pytest.fixturescope参数设为function(默认值),这样每个测试函数都会获得一个全新的fixture实例。
    2. 在Fixture中清理状态:在Fixture的yield语句之后编写清理代码。例如,在auth_client夹具的yield之后,可以添加client.default_headers.pop('Authorization', None)来移除Token。
    3. 设计用例为无状态:尽量让每个用例自己完成所需的前置条件(如登录),而不是依赖其他用例的执行结果。

问题4:断言失败时,Pytest输出的信息不够详细,不知道具体是哪个字段不对

  • 解决
    1. 使用Pytest的-v(详细)和-s(输出打印信息)参数运行:pytest -v -s
    2. 在断言前,先将响应内容打印出来:print(response.json())。但更优雅的方式是使用日志记录。
    3. 使用更智能的断言库:虽然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解析的编码不一致。
  • 解决
    1. 首先检查响应头:print(response.encoding)。Requests会根据HTTP头猜测编码。
    2. 如果猜测错误,可以手动指定:response.encoding = 'utf-8'response.encoding = 'gbk'
    3. 对于响应内容,也可以直接使用response.content.decode('utf-8')来获取字符串。

问题6:POST请求发送JSON数据时,服务器提示参数错误

  • 解决
    1. 检查请求头:确保Content-Type: application/json已正确设置。我们的RequestClient封装已经处理了这一点。
    2. 检查数据格式:使用json.dumps()确保Python字典被正确转换为JSON字符串。同样,我们的封装也处理了。
    3. 使用工具对比:先用Postman或浏览器开发者工具发送一个成功的请求,抓取到原始的HTTP请求数据。然后在你代码中,使用logger.debug将准备发送的数据和请求头完整打印出来,与成功请求进行逐字对比。差异往往在于空格、引号、日期格式等细节。
    4. 如果接口接收的是form-datax-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', files=files, data=data)
    注意:文件句柄使用后最好在适当位置关闭,或者使用with open(...) as f:上下文管理器。

5.4 报告与日志问题

问题8:Allure报告中没有显示我打的日志

  • 原因:Pytest默认不会将print语句或Python的logging输出捕获到Allure结果中。
  • 解决:需要安装并配置allure-pytest插件,它已经处理了与Pythonlogging标准库的集成。确保你的日志是通过logging.getLogger()获得的logger记录的,而不是简单的print。在pytest.ini中,可以添加--capture=sys(默认)或-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(body=response.text, name="Error Response", attachment_type=allure.attachment_type.TEXT) # 如果你有图片的二进制数据 # allure.attach(body=image_data, name="Screenshot", attachment_type=allure.attachment_type.PNG) assert False, f"请求失败,状态码:{response.status_code}"
    附件会在Allure报告的该用例详情中显示出来。

5.5 项目组织与进阶思考

问题10:随着用例增多,如何高效组织和管理?

  • 解决
    1. 目录分层:在test_cases下按业务模块创建子目录,如test_cases/user_management/,test_cases/order_processing/
    2. 使用标记(Mark):在pytest.ini中定义好标记后,可以在用例上用@pytest.mark.smoke装饰。然后可以只运行冒烟测试:pytest -m smoke
    3. 数据驱动:对于大量相似用例(如测试不同登录账号),使用@pytest.mark.parametrize,将测试数据与测试逻辑分离。
    4. 公共夹具提取:将多个模块都需要用到的夹具(如全局初始化、数据库连接)放到test_cases/conftest.py或项目根目录的conftest.py中,Pytest会自动发现。

这个简易项目就像一副骨架,你已经掌握了接口自动化测试的核心肌肉和关节。接下来,你可以根据实际需求,为其增添“血肉”,例如集成CI/CD(Jenkins/GitLab CI)、连接数据库进行数据验证、封装更复杂的断言工具、引入API契约测试(如使用Pact),甚至是搭建一个简单的测试平台。记住,自动化测试不是一蹴而就的,而是一个持续迭代和优化的过程。从这个清晰、可运行的起点出发,每一步扩展都会让你对质量保障体系有更深刻的理解。