基于 Python 自动化接口测试(踩坑与实践)
文档:基于 Python 的自动化接口测试
目录
- 背景
- 问题描述与解决思路
- 核心代码
- 修改点及其详细解释
- 最终测试结果
- 后续优化建议
1. 问题背景
本项目旨在使用 Python 模拟浏览器的请求行为,测试文章分页接口的可用性。测试目标接口如下:
bash
coderboots
http://localhost:8081/api/article/page
接口需要携带与浏览器完全一致的请求头和 Cookies,同时需支持分页参数(如 current
和 size
)。
重点是, 使用postman和浏览器都可以正常测试,但是使用python脚本测试失败。 —遇到网络问题, 一定要记得思考是否是代理问题。
2. 问题描述与解决思路
问题描述
- 初次尝试时,脚本请求失败,返回
502 Bad Gateway
错误。 - 原因分析表明,
requests
库默认继承系统代理配置,而代理拦截或错误转发了请求。 - 此外,初始代码未完全复制浏览器的 Headers 和 Cookies。
解决思路
- 禁用代理:显式设置
proxies
为None
,避免系统代理干扰。 - 完整复制 Headers 和 Cookies:确保请求与浏览器的行为一致。
- 日志改进:详细记录请求 URL、Headers 和响应信息,便于调试和问题定位。
- 重试机制:为网络不稳定的情况添加重试逻辑,提高脚本健壮性。
3. 核心代码
以下是经过优化的测试脚本:
python
coderboots
import requests
import logging
import time
class ArticleApiTest:
def __init__(self):
self.base_url = "http://localhost:8081/api/article"
# 设置请求头,确保与浏览器一致(后面证明只需要修改代理即可)
self.headers = {
}
# 设置Cookies
self.cookies = {
}
# 创建 session 并禁用代理
self.session = requests.Session()
self.session.headers.update(self.headers)
self.session.cookies.update(self.cookies)
self.session.proxies = {'http': None, 'https': None}
# 设置超时时间
self.timeout = 10
# 设置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
self.logger = logging.getLogger(__name__)
def get_page(self, current=1, size=10):
"""发送分页请求"""
url = f"{self.base_url}/page"
params = {'current': current, 'size': size}
try:
# 发送请求
response = self.session.get(url, params=params, timeout=self.timeout, verify=False)
# 记录日志
self.logger.info(f"Request URL: {response.url}")
self.logger.info(f"Response Status: {response.status_code}")
if response.status_code == 200:
self.logger.info("Request successful")
self.logger.info(f"Response Data: {response.json()}")
else:
self.logger.error(f"Request failed with status code: {response.status_code}")
return response
except requests.RequestException as e:
self.logger.error(f"Request failed: {str(e)}")
return None
def main():
tester = ArticleApiTest()
print("开始测试...")
response = tester.get_page()
if response and response.status_code == 200:
print("✅ 测试成功")
else:
print("❌ 测试失败")
if __name__ == "__main__":
main()
4. 修改点及详细解释
修改点 1:禁用代理 --重点和关键
- 原因:初次运行时,系统代理干扰了请求,导致
502 Bad Gateway
错误。 - 解决方法:在
requests.Session
中添加proxies
参数,将http
和https
显式设置为None
。
代码:
python
coderboots
self.session.proxies = {'http': None, 'https': None}
修改点 2:完整的 Headers 和 Cookies
- 原因:部分请求头(如
User-Agent
和sec-ch-ua
)以及 Cookies 在初始代码中未设置,导致服务器未正确识别请求。(由于本次后端其实没有做特别的鉴权,所以这里其实设置为空也可以正常访问) - 解决方法:复制浏览器中的完整 Headers 和 Cookies 并在
requests.Session
中更新。
代码:
python
coderboots
self.headers = {
# 浏览器请求头
}
self.cookies = {
# 浏览器 Cookies
}
修改点 3:日志记录
- 原因:初始代码缺乏详细的日志,不利于调试。
- 解决方法:添加请求 URL、Headers 和响应状态的详细日志记录。
代码:
python
coderboots
self.logger.info(f"Request URL: {response.url}")
self.logger.info(f"Response Status: {response.status_code}")
self.logger.info(f"Response Data: {response.json()}")
5. 最终测试结果
运行脚本后,成功返回分页数据,日志记录如下:
yaml
coderboots
2025-01-08 01:42:21,175 - INFO - Request URL: http://localhost:8081/api/article/page?current=1&size=10
2025-01-08 01:42:21,175 - INFO - Response Status: 200
2025-01-08 01:42:21,176 - INFO - Request successful
2025-01-08 01:42:21,176 - INFO - Response Data: {...}
✅ 测试成功
6. 后续优化建议(略)
- 动态 CSRF Token 支持:
- 如果接口需要动态 Token,可以在发送请求前自动提取并添加到 Headers。
- 重试机制:
- 针对请求失败的情况(如网络不稳定或服务器错误),增加智能重试机制。
- 异步请求:
- 如果需要测试多个接口,可以使用
asyncio
实现异步请求,提高效率。
- 如果需要测试多个接口,可以使用
- 自动化集成:
- 将脚本集成到 CI/CD 管道中,定期验证接口的可用性。
通过上述改进,该脚本现已具备稳定性、可调试性和一致性,能够准确模拟浏览器请求行为并测试目标接口。
最后给一个好用的模版:
修改template 为你的模块名称即可
import requests
import json
from colorama import init, Fore, Style
import os
# 初始化colorama
init()
# 禁用系统代理
os.environ['no_proxy'] = '*'
class TemplateApiTest:
def __init__(self, base_url="http://localhost:8081/api"):
self.base_url = base_url
self.headers = {
"Content-Type": "application/json"
}
def print_response(self, api_name, response):
"""格式化打印响应结果"""
print(f"\n{Fore.CYAN}测试接口:{Style.RESET_ALL} {api_name}")
print(f"{Fore.CYAN}请求URL:{Style.RESET_ALL} {response.url}")
print(f"{Fore.CYAN}状态码:{Style.RESET_ALL} {response.status_code}")
if response.status_code == 200:
print(f"{Fore.GREEN}响应结果:{Style.RESET_ALL}")
try:
formatted_json = json.dumps(response.json(), ensure_ascii=False, indent=2)
print(formatted_json)
except:
print(response.text)
else:
print(f"{Fore.RED}错误响应:{Style.RESET_ALL}")
print(response.text)
print("-" * 80)
def test_get_page(self):
"""测试分页查询模板"""
params = {
"current": 1,
"size": 10,
"category": "通用模板"
}
response = requests.get(
f"{self.base_url}/template/page",
params=params,
headers=self.headers
)
self.print_response("分页查询模板", response)
def test_get_template_by_id(self):
"""测试根据ID获取模板"""
template_id = 1
response = requests.get(
f"{self.base_url}/template/{template_id}",
headers=self.headers
)
self.print_response(f"获取模板(ID: {template_id})", response)
def test_save_template(self):
"""测试保存新模板"""
template_data = {
"name": "测试模板",
"content": "这是一个测试模板的内容",
"category": "通用模板",
"description": "用于测试的模板"
}
response = requests.post(
f"{self.base_url}/template",
headers=self.headers,
data=json.dumps(template_data)
)
self.print_response("创建新模板", response)
def test_update_template(self):
"""测试更新模板"""
template_data = {
"id": 1,
"name": "更新后的测试模板",
"content": "这是更新后的测试模板内容",
"category": "通用模板",
"description": "已更新的测试模板"
}
response = requests.put(
f"{self.base_url}/template",
headers=self.headers,
data=json.dumps(template_data)
)
self.print_response("更新模板", response)
def test_delete_template(self):
"""测试删除模板"""
template_id = 1
response = requests.delete(
f"{self.base_url}/template/{template_id}",
headers=self.headers
)
self.print_response(f"删除模板(ID: {template_id})", response)
def main():
# 创建测试实例
tester = TemplateApiTest()
try:
print(f"\n{Fore.YELLOW}=== 开始测试模板接口 ==={Style.RESET_ALL}")
tester.test_get_page()
tester.test_get_template_by_id()
tester.test_save_template()
tester.test_update_template()
tester.test_delete_template()
print(f"{Fore.YELLOW}=== 模板接口测试完成 ==={Style.RESET_ALL}\n")
except requests.exceptions.RequestException as e:
print(f"{Fore.RED}测试过程中发生错误: {e}{Style.RESET_ALL}")
if __name__ == "__main__":
main()