当前位置: 首页 > news >正文

python 自动化从入门到实战-开发一个接口get post管理请求工具(9)

从零开始打造自己的API测试工具:小白也能看懂的Python实战教程

你是否曾为测试API接口而苦恼?每次都要打开浏览器、找文档、复制粘贴参数…今天,我将带你解读一个自己动手开发的API测试工具,并教你如何把它打包成可执行文件,让你的接口测试工作变得轻松高效!

一、这个工具到底有什么用?

简单来说,这是一个图形界面的API接口测试工具,类似Apifox、Postman这样的专业工具,但更加轻量级。它可以帮助你:

  • 自动加载API文档,无需手动输入接口信息
  • 可视化选择接口,填写参数和请求体
  • 一键发送请求,查看格式化的响应结果
  • 支持多种HTTP方法(GET、POST、PUT等)
  • 自定义请求头和参数
  • 保存响应结果,方便后续分析

预览效果
在这里插入图片描述

二、核心代码解析

接下来,让我们一起看看这个工具是如何实现的。代码虽然有600多行,但核心逻辑其实很清晰,我们可以分几个部分来理解:

1. 基础框架搭建

import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
import requests
import json
import re
from urllib.parse import urlparse
from collections import defaultdictclass APITester:def __init__(self, root):self.root = rootself.root.title("API接口测试工具")self.root.geometry("1200x800")self.root.minsize(1000, 700)# 配置中文字体支持self.style = ttk.Style()self.style.configure("Treeview.Heading",font=("SimHei", 10, "bold"),foreground="#333",background="#f0f0f0")# 初始化变量self.api_spec = Noneself.base_url = ""self.selected_endpoint = tk.StringVar()self.selected_method = tk.StringVar(value="GET")# 创建UIself.create_ui()# 加载API规范self.load_api_spec()

这部分代码是整个工具的基础,主要做了几件事:

  • 导入所需的库(Tkinter用于界面,requests用于发送HTTP请求)
  • 创建一个APITester类来管理整个应用
  • 设置窗口标题、大小和最小尺寸
  • 配置中文字体支持(这很重要,否则中文可能会显示乱码)
  • 初始化一些必要的变量
  • 调用create_ui()创建界面
  • 调用load_api_spec()加载API文档

2. 界面设计

程序使用了create_ui()方法来构建整个界面,设计了一个直观的三栏布局:

  • 左侧面板:显示API端点列表,支持搜索和分类
  • 中间部分:请求配置区域,包括URL、请求方法、请求参数、请求体等
  • 右侧面板:显示响应结果,包括状态码、响应时间、大小和格式化的响应内容
# 请求配置区域的简化代码
request_frame = ttk.LabelFrame(right_panel, text="请求配置", padding="10")
request_frame.pack(fill=tk.BOTH, expand=False, pady=(0, 10))# URL输入框和请求方法选择
base_info_frame = ttk.Frame(request_frame)
# ... 各种输入框、按钮的创建代码 ...# 响应结果区域的简化代码
response_frame = ttk.LabelFrame(right_panel, text="响应结果", padding="10")
response_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))# 响应状态和时间显示
response_info_frame = ttk.Frame(response_frame)
# ... 各种标签、文本框的创建代码 ...

界面设计非常注重用户体验,使用了标签页、表格、按钮等多种UI元素,让整个工具看起来专业且易用。

3. API文档加载与解析

程序启动后,会自动从指定URL加载OpenAPI规范文档:

def load_api_spec(self):try:# 从URL获取OpenAPI规范url = "http://xxxxx:8000/openapi.json"response = requests.get(url, timeout=10)response.raise_for_status()# 解析JSONself.api_spec = response.json()# 提取基本URLif "servers" in self.api_spec and self.api_spec["servers"]:self.base_url = self.api_spec["servers"][0]["url"]# ... 其他处理代码 ...# 填充端点树self.populate_endpoints()# 设置默认请求头self.add_default_headers()except Exception as e:messagebox.showerror("加载失败", f"无法加载OpenAPI规范: {str(e)}")

这段代码做了几件重要的事:

  • 发送HTTP请求获取OpenAPI文档
  • 解析JSON格式的响应
  • 提取API的基础URL
  • 调用populate_endpoints()方法将接口列表填充到左侧面板
  • 设置一些默认的请求头

4. 核心功能:发送请求并处理响应

这是整个工具最核心的部分,send_request()方法负责发送HTTP请求并处理响应:

def send_request(self):try:# 获取URL和方法url = self.url_entry.get().strip()method = self.selected_method.get().upper()if not url:messagebox.showwarning("警告", "请输入URL")return# 准备请求头、参数和请求体headers = {}# ... 从界面获取请求头、参数和请求体的代码 ...# 发送请求并记录时间import timestart_time = time.time()response = requests.request(method=method,url=url,headers=headers,params=params,data=data,json=json_data,timeout=30)end_time = time.time()# 计算请求时间和响应大小request_time = (end_time - start_time) * 1000  # 转换为毫秒response_size = len(response.text.encode('utf-8')) / 1024  # KB# 更新状态信息self.status_var.set(f"状态: {response.status_code}")self.time_var.set(f"时间: {request_time:.2f}ms")self.size_var.set(f"大小: {response_size:.2f}KB")# 显示响应内容self.response_text.delete(1.0, tk.END)# 尝试格式化JSON响应try:json_content = response.json()formatted_json = json.dumps(json_content, indent=2, ensure_ascii=False)self.response_text.insert(tk.END, formatted_json)except ValueError:# 如果不是JSON,直接显示文本self.response_text.insert(tk.END, response.text)# 根据状态码设置文本颜色# ... 状态码处理代码 ...except requests.exceptions.RequestException as e:self.response_text.delete(1.0, tk.END)self.response_text.insert(tk.END, f"请求出错: {str(e)}")self.status_var.set(f"状态: 请求失败")

这个方法实现了完整的HTTP请求发送和响应处理流程:

  • 从界面获取用户输入的URL、请求方法、请求头、参数和请求体
  • 发送HTTP请求,并记录请求时间
  • 解析响应内容,尝试将JSON格式的响应进行格式化,让结果更易读
  • 显示响应状态码、请求时间、响应大小等信息
  • 根据状态码用不同颜色显示结果(成功为绿色,错误为红色)
  • 处理可能出现的异常,给出友好的错误提示

5. 辅助功能:JSON格式化和响应保存

为了让用户体验更好,程序还提供了两个实用的辅助功能:

def format_response_json(self):try:# 获取当前响应文本response_text = self.response_text.get(1.0, tk.END).strip()if not response_text:return# 尝试解析并格式化JSONjson_content = json.loads(response_text)formatted_json = json.dumps(json_content, indent=2, ensure_ascii=False)# 更新文本框self.response_text.delete(1.0, tk.END)self.response_text.insert(tk.END, formatted_json)except json.JSONDecodeError:messagebox.showinfo("提示", "响应内容不是有效的JSON格式,无法格式化")

这个方法允许用户手动格式化响应内容,即使第一次自动格式化失败了,也可以尝试手动格式化。

def save_response(self):# 获取响应文本response_text = self.response_text.get(1.0, tk.END).strip()if not response_text:messagebox.showinfo("提示", "没有可保存的响应内容")return# 打开文件对话框file_path = filedialog.asksaveasfilename(defaultextension=".txt",filetypes=[("JSON文件", "*.json"), ("文本文件", "*.txt"), ("所有文件", "*.*")],title="保存响应")if file_path:try:with open(file_path, "w", encoding="utf-8") as f:f.write(response_text)messagebox.showinfo("成功", f"响应已保存到: {file_path}")except Exception as e:messagebox.showerror("保存失败", f"无法保存响应: {str(e)}")

这个方法让用户可以将响应结果保存到本地文件,方便后续分析或分享给他人。

三、如何将工具打包成可执行文件?

现在,我们已经了解了这个API测试工具的核心功能和实现原理,接下来我将教你如何把它打包成一个可以独立运行的exe文件,方便在没有安装Python的电脑上使用。

1. 安装PyInstaller

首先,我们需要安装一个名为PyInstaller的工具,它可以帮助我们将Python代码打包成exe文件:

pip install pyinstaller

2. 创建打包配置文件

接下来,我们需要创建一个打包配置文件,这个文件会告诉PyInstaller如何打包我们的应用。在代码目录下创建一个名为api_tester.spec的文件,内容如下:

# -*- mode: python ; coding: utf-8 -*-block_cipher = Nonea = Analysis(['d:\\PyStudy\\MysqlManagerTool\\openapi\\open.py'],pathex=['d:\\PyStudy\\MysqlManagerTool\\openapi'],binaries=[],datas=[],hiddenimports=['requests', 'json', 'tkinter'],hookspath=[],hooksconfig={},runtime_hooks=[],excludes=[],win_no_prefer_redirects=False,win_private_assemblies=False,cipher=block_cipher,noarchive=False)pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)exe = EXE(pyz,a.scripts,[],exclude_binaries=True,name='API接口测试工具',debug=False,bootloader_ignore_signals=False,strip=False,upx=True,console=False,disable_windowed_traceback=False,target_arch=None,codesign_identity=None,entitlements_file=None)coll = COLLECT(exe,a.binaries,a.zipfiles,a.datas,strip=False,upx=True,upx_exclude=[],name='API接口测试工具')

这个配置文件的主要参数解释:

  • Analysis:指定要分析的Python文件、路径和依赖
  • pathex:指定Python文件的路径
  • hiddenimports:指定需要隐藏导入的模块
  • name:生成的可执行文件的名称
  • console:是否显示控制台窗口,这里设置为False表示不显示
  • upx:是否使用UPX压缩,这里设置为True可以减小文件体积

3. 执行打包命令

最后,打开命令提示符,切换到配置文件所在的目录,执行以下命令开始打包:

pyinstaller api_tester.spec

打包过程可能需要一些时间,完成后,你会在当前目录下看到一个dist文件夹,里面有一个名为API接口测试工具的文件夹,其中包含了可执行文件API接口测试工具.exe

4. 运行打包后的程序

双击API接口测试工具.exe文件,你会看到熟悉的界面,现在你可以在任何没有安装Python的电脑上运行这个工具了!

四、完整代码

import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
import requests
import json
import re
from urllib.parse import urlparse
from collections import defaultdictclass APITester:def __init__(self, root):self.root = rootself.root.title("API接口测试工具")self.root.geometry("1200x800")self.root.minsize(1000, 700)# 配置中文字体支持self.style = ttk.Style()self.style.configure("Treeview.Heading",font=("SimHei", 10, "bold"),foreground="#333",background="#f0f0f0")# 初始化变量self.api_spec = Noneself.base_url = ""self.selected_endpoint = tk.StringVar()self.selected_method = tk.StringVar(value="GET")# 创建UIself.create_ui()# 加载API规范self.load_api_spec()def create_ui(self):# 创建主框架main_frame = ttk.Frame(self.root, padding="10")main_frame.pack(fill=tk.BOTH, expand=True)# 左侧面板 - API端点选择left_panel = ttk.LabelFrame(main_frame, text="API端点", padding="10")left_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=False, padx=(0, 10))# 搜索框search_frame = ttk.Frame(left_panel)search_frame.pack(fill=tk.X, padx=5, pady=5)ttk.Label(search_frame, text="搜索端点:", width=10).pack(side=tk.LEFT)self.search_entry = ttk.Entry(search_frame)self.search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)self.search_entry.bind("<KeyRelease>", self.filter_endpoints)# 端点列表self.endpoint_frame = ttk.Frame(left_panel)self.endpoint_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)self.endpoint_tree = ttk.Treeview(self.endpoint_frame)self.endpoint_tree.heading("#0", text="可用端点")self.endpoint_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)scrollbar = ttk.Scrollbar(self.endpoint_frame, orient=tk.VERTICAL, command=self.endpoint_tree.yview)scrollbar.pack(side=tk.RIGHT, fill=tk.Y)self.endpoint_tree.config(yscrollcommand=scrollbar.set)self.endpoint_tree.bind("<<TreeviewSelect>>", self.on_endpoint_select)# 右侧面板right_panel = ttk.Frame(main_frame)right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)# 请求配置request_frame = ttk.LabelFrame(right_panel, text="请求配置", padding="10")request_frame.pack(fill=tk.BOTH, expand=False, pady=(0, 10))# 基本请求信息base_info_frame = ttk.Frame(request_frame)base_info_frame.pack(fill=tk.X, padx=5, pady=5)ttk.Label(base_info_frame, text="URL:", width=5).grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)self.url_entry = ttk.Entry(base_info_frame)self.url_entry.grid(row=0, column=1, sticky=tk.EW, padx=5, pady=2)ttk.Label(base_info_frame, text="方法:", width=5).grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)method_frame = ttk.Frame(base_info_frame)method_frame.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]for method in methods:ttk.Radiobutton(method_frame, text=method, variable=self.selected_method, value=method).pack(side=tk.LEFT, padx=5)# 选项卡tab_control = ttk.Notebook(right_panel)tab_control.pack(fill=tk.BOTH, expand=True)# 请求参数选项卡self.params_tab = ttk.Frame(tab_control)tab_control.add(self.params_tab, text="请求参数")# 创建参数表格self.params_frame = ttk.Frame(self.params_tab)self.params_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)columns = ("name", "value", "description")self.params_tree = ttk.Treeview(self.params_frame, columns=columns, show="headings")for col in columns:self.params_tree.heading(col, text=col)if col == "name":self.params_tree.column(col, width=120)elif col == "value":self.params_tree.column(col, width=200)else:self.params_tree.column(col, width=250)self.params_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)# 请求体选项卡self.body_tab = ttk.Frame(tab_control)tab_control.add(self.body_tab, text="请求体")# 选择请求体格式body_format_frame = ttk.Frame(self.body_tab)body_format_frame.pack(fill=tk.X, padx=5, pady=5)ttk.Label(body_format_frame, text="格式:", width=5).pack(side=tk.LEFT, padx=5)self.body_format = tk.StringVar(value="JSON")format_combo = ttk.Combobox(body_format_frame, textvariable=self.body_format, values=["JSON", "Form Data", "Text"], state="readonly", width=10)format_combo.pack(side=tk.LEFT, padx=5)# 请求体编辑框body_edit_frame = ttk.Frame(self.body_tab)body_edit_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)self.body_text = scrolledtext.ScrolledText(body_edit_frame, wrap=tk.WORD, width=60, height=10, font=("SimHei", 10))self.body_text.pack(fill=tk.BOTH, expand=True)# 请求头选项卡self.headers_tab = ttk.Frame(tab_control)tab_control.add(self.headers_tab, text="请求头")# 创建请求头表格headers_frame = ttk.Frame(self.headers_tab)headers_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)header_columns = ("name", "value")self.headers_tree = ttk.Treeview(headers_frame, columns=header_columns, show="headings")for col in header_columns:self.headers_tree.heading(col, text=col)if col == "name":self.headers_tree.column(col, width=150)else:self.headers_tree.column(col, width=300)self.headers_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)# 添加请求头按钮add_header_btn = ttk.Button(headers_frame, text="添加请求头", command=self.add_header)add_header_btn.pack(side=tk.BOTTOM, padx=5, pady=5)# 响应结果response_frame = ttk.LabelFrame(right_panel, text="响应结果", padding="10")response_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))# 响应状态和时间response_info_frame = ttk.Frame(response_frame)response_info_frame.pack(fill=tk.X, padx=5, pady=5)self.status_var = tk.StringVar(value="状态: 未请求")self.time_var = tk.StringVar(value="时间: 0ms")self.size_var = tk.StringVar(value="大小: 0KB")ttk.Label(response_info_frame, textvariable=self.status_var, width=20).pack(side=tk.LEFT, padx=5)ttk.Label(response_info_frame, textvariable=self.time_var, width=20).pack(side=tk.LEFT, padx=5)ttk.Label(response_info_frame, textvariable=self.size_var, width=20).pack(side=tk.LEFT, padx=5)# 格式化按钮format_btn = ttk.Button(response_info_frame, text="格式化JSON", command=self.format_response_json)format_btn.pack(side=tk.RIGHT, padx=5)# 保存按钮save_btn = ttk.Button(response_info_frame, text="保存响应", command=self.save_response)save_btn.pack(side=tk.RIGHT, padx=5)# 响应文本框response_text_frame = ttk.Frame(response_frame)response_text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)self.response_text = scrolledtext.ScrolledText(response_text_frame, wrap=tk.WORD, width=80, height=15, font=("SimHei", 10))self.response_text.pack(fill=tk.BOTH, expand=True)# 发送请求按钮send_btn = ttk.Button(right_panel, text="发送请求", command=self.send_request, style="Accent.TButton")send_btn.pack(fill=tk.X, pady=10, padx=10)# 自定义强调按钮样式self.style.configure("Accent.TButton", foreground="blue", font=("SimHei", 10, "bold"))def load_api_spec(self):try:# 从URL获取OpenAPI规范url = "http://14.103.236.44:8000/openapi.json"response = requests.get(url, timeout=10)response.raise_for_status()# 解析JSONself.api_spec = response.json()# 提取基本URLif "servers" in self.api_spec and self.api_spec["servers"]:self.base_url = self.api_spec["servers"][0]["url"]elif "host" in self.api_spec:scheme = self.api_spec.get("schemes", ["http"])[0]self.base_url = f"{scheme}://{self.api_spec['host']}{self.api_spec.get('basePath', '')}"# 填充端点树self.populate_endpoints()# 设置默认请求头self.add_default_headers()except Exception as e:messagebox.showerror("加载失败", f"无法加载OpenAPI规范: {str(e)}")def populate_endpoints(self):if not self.api_spec:return# 清空现有树for item in self.endpoint_tree.get_children():self.endpoint_tree.delete(item)# 按路径组织端点paths = self.api_spec.get("paths", {})grouped_paths = defaultdict(list)# 提取路径组for path, methods in paths.items():# 尝试提取基本路径作为组名parts = path.split('/')[1:]if parts:group_name = parts[0] if parts else "其他"else:group_name = "根路径"grouped_paths[group_name].append((path, methods))# 添加到树视图for group, paths in grouped_paths.items():parent = self.endpoint_tree.insert("", tk.END, text=group)for path, methods in paths:# 为每个路径创建子项path_item = self.endpoint_tree.insert(parent, tk.END, text=path, values=(path,))# 为每个HTTP方法创建子项for method, details in methods.items():operation_id = details.get("operationId", method.upper())summary = details.get("summary", "")display_text = f"{method.upper()}: {operation_id}"if summary:display_text += f" ({summary})"self.endpoint_tree.insert(path_item, tk.END, text=display_text, values=(path, method.upper(), operation_id, details))def filter_endpoints(self, event=None):search_text = self.search_entry.get().lower()# 遍历所有项for item in self.endpoint_tree.get_children(""):# 检查父项parent_text = self.endpoint_tree.item(item, "text").lower()show_parent = search_text in parent_text# 检查子项for child in self.endpoint_tree.get_children(item):child_text = self.endpoint_tree.item(child, "text").lower()show_child = search_text in child_textif show_child:show_parent = Trueself.endpoint_tree.item(child, open=show_child)self.endpoint_tree.item(item, open=show_parent)def on_endpoint_select(self, event=None):selected_item = self.endpoint_tree.selection()if not selected_item:returnitem = selected_item[0]values = self.endpoint_tree.item(item, "values")# 检查是否是叶子节点(包含方法信息)if len(values) >= 2 and values[1]:path, method, operation_id, details = values# 设置URLfull_url = f"{self.base_url}{path}" if not path.startswith("http") else pathself.url_entry.delete(0, tk.END)self.url_entry.insert(0, full_url)# 设置方法self.selected_method.set(method)# 填充参数self.populate_parameters(details)# 填充请求体示例self.populate_request_body(details)def populate_parameters(self, details):# 清空现有参数for item in self.params_tree.get_children():self.params_tree.delete(item)# 获取参数parameters = details.get("parameters", [])for param in parameters:name = param.get("name", "")description = param.get("description", "")# 插入参数到表格self.params_tree.insert("", tk.END, values=(name, "", description))def populate_request_body(self, details):# 清空请求体self.body_text.delete(1.0, tk.END)# 获取请求体信息request_body = details.get("requestBody", {})if not request_body:return# 获取JSON内容类型的schemacontent = request_body.get("content", {})json_content = content.get("application/json", {})if json_content:schema = json_content.get("schema", {})example = json_content.get("example", None)# 如果有示例,使用示例if example:formatted_json = json.dumps(example, indent=2, ensure_ascii=False)self.body_text.insert(tk.END, formatted_json)# 否则尝试从schema生成示例elif schema:try:example_obj = self.generate_example_from_schema(schema)formatted_json = json.dumps(example_obj, indent=2, ensure_ascii=False)self.body_text.insert(tk.END, formatted_json)except Exception:passdef generate_example_from_schema(self, schema):"""从schema生成示例数据"""if "example" in schema:return schema["example"]elif "enum" in schema:return schema["enum"][0] if schema["enum"] else ""elif "type" not in schema:return ""type_value = schema["type"]if type_value == "string":format_value = schema.get("format", "")if format_value == "date-time":return "2023-01-01T12:00:00Z"return "示例文本"elif type_value == "integer":return 1elif type_value == "number":return 1.0elif type_value == "boolean":return Trueelif type_value == "array":items = schema.get("items", {})return [self.generate_example_from_schema(items)]elif type_value == "object":result = {}properties = schema.get("properties", {})required = schema.get("required", [])# 至少包含一个属性,即使不是必需的if not required and properties:required = [next(iter(properties))]for prop_name in required:if prop_name in properties:result[prop_name] = self.generate_example_from_schema(properties[prop_name])return resultreturn ""def add_default_headers(self):# 添加常见请求头default_headers = {"Content-Type": "application/json","Accept": "application/json"}for name, value in default_headers.items():self.headers_tree.insert("", tk.END, values=(name, value))def add_header(self):# 创建添加请求头的对话框dialog = tk.Toplevel(self.root)dialog.title("添加请求头")dialog.geometry("400x150")dialog.resizable(False, False)# 居中显示dialog.geometry("+".join(map(str, (self.root.winfo_x() + 200, self.root.winfo_y() + 200))))# 创建输入框ttk.Label(dialog, text="名称:", width=10).grid(row=0, column=0, sticky=tk.W, padx=20, pady=20)name_entry = ttk.Entry(dialog, width=25)name_entry.grid(row=0, column=1, padx=10, pady=20)ttk.Label(dialog, text="值:", width=10).grid(row=1, column=0, sticky=tk.W, padx=20, pady=5)value_entry = ttk.Entry(dialog, width=25)value_entry.grid(row=1, column=1, padx=10, pady=5)# 添加按钮def save_header():name = name_entry.get().strip()value = value_entry.get().strip()if name:self.headers_tree.insert("", tk.END, values=(name, value))dialog.destroy()else:messagebox.showwarning("警告", "请求头名称不能为空")btn_frame = ttk.Frame(dialog)btn_frame.grid(row=2, column=0, columnspan=2, pady=10)ttk.Button(btn_frame, text="确定", command=save_header).pack(side=tk.LEFT, padx=10)ttk.Button(btn_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=10)def send_request(self):try:# 获取URL和方法url = self.url_entry.get().strip()method = self.selected_method.get().upper()if not url:messagebox.showwarning("警告", "请输入URL")return# 准备请求头headers = {}for item in self.headers_tree.get_children():name, value = self.headers_tree.item(item, "values")if name and value:headers[name.strip()] = value.strip()# 准备请求参数params = {}for item in self.params_tree.get_children():name, value, _ = self.params_tree.item(item, "values")if name and value:params[name.strip()] = value.strip()# 准备请求体data = Nonejson_data = Nonebody_text = self.body_text.get(1.0, tk.END).strip()if body_text:content_type = headers.get("Content-Type", "").lower()body_format = self.body_format.get().lower()if body_format == "json" or "json" in content_type:try:json_data = json.loads(body_text)except json.JSONDecodeError:messagebox.showerror("JSON错误", "请求体不是有效的JSON格式")returnelif body_format == "form data" or "form" in content_type:# 简单解析表单数据 key=value&key2=value2data = {}pairs = body_text.split('&')for pair in pairs:if '=' in pair:key, value = pair.split('=', 1)data[key] = valueelse:data = body_text# 发送请求并记录时间import timestart_time = time.time()response = requests.request(method=method,url=url,headers=headers,params=params,data=data,json=json_data,timeout=30)end_time = time.time()# 计算请求时间request_time = (end_time - start_time) * 1000  # 转换为毫秒# 更新状态信息self.status_var.set(f"状态: {response.status_code}")self.time_var.set(f"时间: {request_time:.2f}ms")# 计算响应大小response_size = len(response.text.encode('utf-8')) / 1024  # KBself.size_var.set(f"大小: {response_size:.2f}KB")# 显示响应内容self.response_text.delete(1.0, tk.END)# 尝试格式化JSON响应try:json_content = response.json()formatted_json = json.dumps(json_content, indent=2, ensure_ascii=False)self.response_text.insert(tk.END, formatted_json)except ValueError:# 如果不是JSON,直接显示文本self.response_text.insert(tk.END, response.text)# 根据状态码设置文本颜色if 200 <= response.status_code < 300:self.status_var.set(f"状态: {response.status_code} (成功)")self.response_text.tag_configure("status", foreground="green")elif 400 <= response.status_code < 500:self.status_var.set(f"状态: {response.status_code} (客户端错误)")self.response_text.tag_configure("status", foreground="orange")elif 500 <= response.status_code:self.status_var.set(f"状态: {response.status_code} (服务器错误)")self.response_text.tag_configure("status", foreground="red")except requests.exceptions.RequestException as e:self.response_text.delete(1.0, tk.END)self.response_text.insert(tk.END, f"请求出错: {str(e)}")self.status_var.set(f"状态: 请求失败")except Exception as e:self.response_text.delete(1.0, tk.END)self.response_text.insert(tk.END, f"发生错误: {str(e)}")self.status_var.set(f"状态: 未知错误")def format_response_json(self):try:# 获取当前响应文本response_text = self.response_text.get(1.0, tk.END).strip()if not response_text:return# 尝试解析并格式化JSONjson_content = json.loads(response_text)formatted_json = json.dumps(json_content, indent=2, ensure_ascii=False)# 更新文本框self.response_text.delete(1.0, tk.END)self.response_text.insert(tk.END, formatted_json)except json.JSONDecodeError:messagebox.showinfo("提示", "响应内容不是有效的JSON格式,无法格式化")def save_response(self):# 获取响应文本response_text = self.response_text.get(1.0, tk.END).strip()if not response_text:messagebox.showinfo("提示", "没有可保存的响应内容")return# 打开文件对话框file_path = filedialog.asksaveasfilename(defaultextension=".txt",filetypes=[("JSON文件", "*.json"), ("文本文件", "*.txt"), ("所有文件", "*.*")],title="保存响应")if file_path:try:with open(file_path, "w", encoding="utf-8") as f:f.write(response_text)messagebox.showinfo("成功", f"响应已保存到: {file_path}")except Exception as e:messagebox.showerror("保存失败", f"无法保存响应: {str(e)}")if __name__ == "__main__":root = tk.Tk()app = APITester(root)root.mainloop()

五、写在最后

通过这个API测试工具的开发和打包,我们不仅学习了如何使用Python开发一个实用的图形界面应用,还掌握了将Python程序打包成可执行文件的方法。这个工具虽然简单,但功能实用,可以大大提高API测试的效率。

如果你对这个工具有兴趣,或者想进一步学习Python编程,可以自己动手尝试修改和扩展这个工具,比如添加更多的功能、优化界面设计等。编程的乐趣就在于不断地探索和创造!

希望这篇文章能对你有所帮助,如果你觉得有用,别忘了分享给你的朋友哦!


文章转载自:

http://4NDlzF9m.tmbtm.cn
http://EJu9FC3N.tmbtm.cn
http://i0RDS8Mv.tmbtm.cn
http://U41PeGO1.tmbtm.cn
http://lvXdPLUF.tmbtm.cn
http://FAbqJ0sb.tmbtm.cn
http://gDsZPVmI.tmbtm.cn
http://wh0MvVSu.tmbtm.cn
http://a2Ae0mct.tmbtm.cn
http://F8pLaVLL.tmbtm.cn
http://ZlFZq04K.tmbtm.cn
http://w4udLKQE.tmbtm.cn
http://lY86GuPP.tmbtm.cn
http://vhgkCNgO.tmbtm.cn
http://1L107PtC.tmbtm.cn
http://34HsmojX.tmbtm.cn
http://kMcV0E1f.tmbtm.cn
http://et7M4MSN.tmbtm.cn
http://WXF1eKIy.tmbtm.cn
http://jMlRZ6HY.tmbtm.cn
http://dmKauS4T.tmbtm.cn
http://56obBt1a.tmbtm.cn
http://uuoH2IRQ.tmbtm.cn
http://wJaJ9ZIt.tmbtm.cn
http://buigfVrw.tmbtm.cn
http://jNcRWiYU.tmbtm.cn
http://Z2zCRkOb.tmbtm.cn
http://FvfHNkb2.tmbtm.cn
http://biTLvlI8.tmbtm.cn
http://OLCMryYz.tmbtm.cn
http://www.dtcms.com/a/385814.html

相关文章:

  • 认知语义学中的意象图式对AI自然语言处理中隐喻分析的影响与启示
  • Edge浏览器的自动化点击系统
  • 达梦数据库巡检常用语句
  • 基于Spring Cloud Gateway的全链路限流策略对比与实践指南
  • ​Oracle存储的实现:一个8KB块能存储多少行数据?​​一个块存不下一行数据会出现什么情况?
  • React学习教程,从入门到精通,React 组件事件处理语法知识点及使用方法(21)
  • ChatGPT 辅助重构:老旧 jQuery 项目迁移到 React 的协作日志
  • 嵌入式数据结构笔记五——循环链表内核链表
  • C++与Lua交互:从原理到实践指南
  • 状态管理:在 Next.js 中使用 React Context 或 Zustand
  • SeaweedFS深度解析(九):k8s环境使用helm部署Seaweedfs集群
  • uniApp开发XR-Frame微信小程序创建3D场景 (8) 刚体碰撞
  • NPM 常用命令
  • Windows 11 安装使用 nvm,Node.js、npm多版本管理、切换
  • AI Compass前沿速览:GPT-5-Codex 、宇树科技世界模型、InfiniteTalk美团数字人、ROMA多智能体框架、混元3D 3.0
  • 苹果上架全流程指南 苹果应用上架步骤、iOS 应用发布流程、uni-app 打包上传 ipa 与 App Store 审核经验分享
  • 旗讯 OCR 识别系统深度解析:一站式解决表格、手写文字、证件识别难题!
  • strip()函数使用注意点
  • 好用的开源日志库:Easylogger解析与移植STM32
  • django入门-数据库基本操作
  • springboot的项目实现excel上传功能
  • 从 Docker 守护进程获取实时事件
  • TCP编程:socket概念及使用方法(基础教程)
  • Python 在运维与云原生领域的核心应用:从基础到实践
  • 项目实战:Rsync + Sersync 实现文件实时同步
  • 云原生是什么
  • Docker 镜像瘦身实战:从 1.2GB 压缩到 200MB 的优化过程
  • RabbitMQ消息中间件
  • 2019年下半年 系统架构设计师 案例分析
  • OpenAI编程模型重磅升级!GPT-5-Codex发布,动态思考机制实现编程效率倍增