【GUI开发】Tkinter详解
GUI开发工具
-
Java
- Swing
- JavaFX
- Apache Pivot
- SWT
- NetBeans Platform
-
Python
- Tkinter
Tkinter是Python的标准GUI库,提供创建窗口、按钮、文本框和其他GUI组件的工具。它是一个轻量级的库,简单易用,适合初学者。 - PyQt
PyQt是Qt库的Python绑定,支持跨平台开发,包括Windows、Mac OS和Unix系统。PyQt提供了丰富的GUI组件和工具,适用于需要复杂功能的项目。 - wxPython
wxPython是wxWidgets的Python封装,也是一个跨平台的GUI库。它提供了丰富的控件和良好的跨平台能力,适用于需要高性能和良好用户体验的应用。 - Kivy
Kivy是一个开源的Python库,用于开发多触摸应用程序。它支持Android、iOS、Linux、OS X和Windows平台。Kivy适用于需要良好触摸支持的应用程序。 - Pygame
虽然Pygame主要用于游戏开发,但它也可以用于简单的GUI应用。它提供了绘图和动画功能,适合需要图形界面的简单应用。
- Tkinter
Tkinter介绍
Tkinter是Python标准库中的一个GUI(Graphical User Interface,图形用户界面)工具包,其目的是为Python开发者提供快捷创建GUI应用程序的方式。
Tkinter基于Tcl/Tk图形库,允许我们使用Python代码来创建和管理窗口、标签、按钮、复选框、文本框、列表框、滚动条、画布、菜单等多种控件和组件。 Tkinter对多数平台都有良好的支持,而无需安装额外的软件或库。
通过Tkinter编写的GUI程序可以运行在像Windows、Mac OS X和Linux这样广泛流行的操作系统上,并允许用户与程序进行交互操作。 Tkinter提供了简单易懂的API,可以使得初学者也可以迅速学会如何构建Python GUI应用程序。
Tkinter创建窗口
import tkinter as tk # 在代码里面导入库,起一个别名,以后代码里面就用这个别名
root = tk.Tk() # 这个库里面有Tk()这个方法,这个方法的作用就是创建一个窗口
root.title('演示窗口')
root.geometry("300x100+630+80") # 窗口设置及所在屏幕位置(宽度x高度)+(x轴+y轴)
btn1 = tk.Button(root) # 创建按钮,加载到root窗口内
btn1["text"] = "按钮1" # 按钮命名
btn1.pack() # 按钮布局(按钮在窗口里面的定位)
# 创建点击按钮事件的弹窗,先导入messagebox,这个必须单独导入
from tkinter import messagebox
def test(e):
messagebox.showinfo("窗口名称", "点击成功")
# 将按钮和方法进行绑定,也就是创建了一个事件
btn1.bind("<Button-1>", test) # 第一个参数为:按鼠标左键的事件 第二个参数为:要执行的方法的名字
root.mainloop() # 让窗口一直显示,循环
组件布局及样式
3种布局管理器:
- pack
这个布局管理器,要么将组件垂直的排列,要么水平的排列 - grid
Grid(网格)布局管理器会将控件放置到一个二维的表格里。
主控件被分割成一系列的行和列,表格中的每个单元(cell)都可以放置一个控件。 - place
place布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景
grid
| 选项 | 说明 |
|---|---|
| column | 单元格的列号,从0开始的正整数 |
| columnspan | 跨列,跨越的列数,正整数 |
| row | 单元格的行号, 从0开始的正整数 |
| rowspan | 跨行,跨越的行数,正整数 |
| ipadx, ipady | 设置子组件之间的间隔,x方向或y方向,默认单位为像素,非浮点数,默认0.0 |
| padx, pady | 与之并列的组件之间的间隔,x方向或y方向,默认单位为像素,非浮点数,默认0.0 |
| sticky | 组件紧贴所在的单元格的某一脚,对应于东南西北中以及4个角。东 = “e”,南=“s”,西=“w”,北=“n”,“ne”,“se”,“sw”, “nw”; |
btn1 = tk.Button(root)
btn1["text"] = "按钮1"
# btn1.grid() # column所在列位置
btn1.grid(column=0, # column所在列位置
columnspan=2, # columnspan控件横跨的列数
row=2, # row控件所在的行
rowspan=5, # rowspan控件横跨的行数
ipadx=20, # 水平方向内边距(按钮长)
ipady=20, # 垂直方向内边距(按钮高)
padx=50, # 水平方向外边距
pady=50, # 垂直方向外边距
sticky="w" # 组件东南西北的方向
)
print(btn1.grid_info()) # 打印grid组件默认参数
# 按钮2
btn2 = tk.Button(root)
btn2["text"] = "按钮2"
btn2.grid(column=1,
row=1
)
root.title('演示窗口')
root.geometry("300x150+1000+300")
root.mainloop()
place
| 选项 | 说明 |
|---|---|
| x,y | 组件左上角的绝对坐标(相当于窗口) |
| relx ,rely | 组件左上角的坐标(相对于父容器) |
| width , height | 组件的宽度和高度 |
| relwidth , relheight | 组件的宽度和高度(相对于父容器) |
| anchor | 对齐方式,左对齐“w”,右对齐“e”,顶对齐“n”,底对齐“s” |
but1 = tk.Button(root, text="按钮1")
but1.place(relx=0.2, x=100, y=20, relwidth=0.2, relheight=0.5)
组件文字字体、字号、字体粗细、颜色设置
from tkinter import font
font_1 = font.Font(family='Helvetica', size=30, weight='normal')
font_2 = font.Font(family='Arial', size=15, weight='bold')
"""
family:指定字体名称
size:指定字体大小
weight:指定字体的粗细程度
"""
but1 = tk.Button(root, text="背景色", font=font_1, bg="LightSkyBlue") # bg:背景颜色
but1.grid(row=0, column=0)
Label1 = tk.Label(root, text="文字颜色", font=font_2, foreground="Orange") # foreground: 文字颜色
Label1.grid(row=0, column=2)
root.title('演示窗口')
root.geometry("300x150+1000+300")
root.mainloop()
基本控件介绍
封装
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('演示窗口')
self.root.geometry("500x200+1100+150")
self.interface()
def interface(self):
""""界面编写位置"""
pass
文本显示_Label
def interface(self):
""""界面编写位置"""
self.Label0 = tk.Label(self.root, text="文本显示")
self.Label0.grid(row=0, column=0)
按钮显示_Button
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="按钮显示")
self.Button0.grid(row=0, column=0)
输入框显示_Entry
def interface(self):
""""界面编写位置"""
self.Entry0 = tk.Entry(self.root)
self.Entry0.grid(row=0, column=0)
文本输入框显示_Text
# pack布局
def interface(self):
""""界面编写位置"""
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.pack(pady=0, padx=30)
# grid布局
def interface(self):
""""界面编写位置"""
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=1, column=0)
复选按钮_Checkbutton
def interface(self):
""""界面编写位置"""
self.Checkbutton01 = tk.Checkbutton(self.root, text="名称")
self.Checkbutton01.grid(row=0, column=2)
单选按钮_Radiobutton
def interface(self):
""""界面编写位置"""
self.Radiobutton01 = tk.Radiobutton(self.root, text="名称")
self.Radiobutton01.grid(row=0, column=2)
下拉选择框_Combobox
def interface(self):
""""界面编写位置"""
values = ['1', '2', '3', '4']
self.combobox = ttk.Combobox(
master=self.root, # 父容器
height=10, # 高度,下拉显示的条目数量
width=20, # 宽度
state='', # 设置状态 normal(可选可输入)、readonly(只可选)、 disabled(禁止输入选择)
cursor='arrow', # 鼠标移动时样式 arrow, circle, cross, plus...
font=('', 15), # 字体、字号
textvariable='', # 通过StringVar设置可改变的值
values=values, # 设置下拉框的选项
)
self.combobox.grid(padx=150)
菜单-主菜单、子菜单
# 创建主菜单实例
self.menubar = Menu(self.root)
# 显示菜单,将root根窗口的主菜单设置为menu
self.root.config(menu=self.menubar)
def interface(self):
""""界面编写位置"""
# 在 menubar 上设置菜单名,并关联一系列子菜单
self.menubar.add_cascade(label="文件", menu=self.papers())
self.menubar.add_cascade(label="查看", menu=self.about())
def papers(self):
"""
fmenu = Menu(self.menubar): 创建子菜单实例
tearoff=1: 1的话多了一个虚线,如果点击的话就会发现,这个菜单框可以独立出来显示
fmenu.add_separator(): 添加分隔符"--------"
"""
fmenu = Menu(self.menubar, tearoff=0)
# 创建单选框
for item in ['新建', '打开', '保存', '另存为']:
fmenu.add_command(label=item)
return fmenu
def about(self):
amenu = Menu(self.menubar, tearoff=0)
# 添加复选框
for item in ['项目复选框', '文件扩展名', '隐藏的项目']:
amenu.add_checkbutton(label=item)
return amenu
if __name__ == '__main__':
a = GUI()
a.root.mainloop()
组件使用方法介绍
按钮(Button)绑定事件
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="运行", command=self.event)
self.Button0.grid(row=0, column=0, padx=20)
# 使用lambda表达式创建了一个匿名函数,该函数会调用self.add_page方法并传递参数
self.Button1 = tk.Button(self.root, text="确定", command=lambda: self.parameter('测试'))
self.Button1.grid(row=0, column=1, padx=20)
self.Button2 = tk.Button(self.root, text="退出", command=self.root.destroy, bg="Gray") # bg=颜色
self.Button2.grid(row=0, column=2, padx=20)
def event(self):
"""按钮事件"""
print("运行成功")
def parameter(self, data):
"""传入参数"""
print(f"获取到的参数: {data}")
if __name__ == '__main__':
a = GUI()
a.root.mainloop()
输入框(Entry)内容获取
def interface(self):
""""界面编写位置"""
self.entry00 = tk.StringVar()
self.entry00.set("默认信息")
self.entry0 = tk.Entry(self.root, textvariable=self.entry00)
self.entry0.grid(row=1, column=0)
self.Button0 = tk.Button(self.root, text="运行", command=self.event)
self.Button0.grid(row=0, column=0)
def event(self):
"""按钮事件,获取文本信息"""
a = self.entry00.get()
print(a)
if __name__ == '__main__':
a = GUI()
a.root.mainloop()
文本输入框(Text),写入文本信息和清除文本信息
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="清除", command=self.event)
self.Button0.grid(row=0, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=1, column=0)
self.w1.insert("insert", "默认信息")
def event(self):
'''清空输入框'''
self.w1.delete(1.0, "end")
获取复选按钮(Checkbutton)的状态
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.event)
self.Button0.grid(row=0, column=0)
self.v1 = tk.IntVar()
self.Checkbutton01 = tk.Checkbutton(self.root, text="复选框", command=self.Check_box, variable=self.v1)
self.Checkbutton01.grid(row=1, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=2, column=0)
def event(self):
'''按钮事件,获取复选框的状态,1表示勾选,0表示未勾选'''
a = self.v1.get()
self.w1.insert(1.0, str(a)+'\n')
def Check_box(self):
'''复选框事件'''
if self.v1.get() == 1:
self.w1.insert(1.0, "勾选"+'\n')
else:
self.w1.insert(1.0, "未勾选"+'\n')
清除控件
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.event)
self.Button0.grid(row=0, column=0)
self.Label0 = tk.Label(self.root, text="文本显示")
self.Label0.grid(row=1, column=0)
self.Entry0 = tk.Entry(self.root)
self.Entry0.grid(row=2, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=3, column=0)
def event(self):
'''按钮事件,清除Label、Entry、Text组件'''
a = [self.Label0, self.Entry0, self.w1]
for i in a:
i.grid_forget()
清除复选框勾选状态
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.event)
self.Button0.grid(row=0, column=0)
self.v1 = tk.IntVar()
self.Checkbutton01 = tk.Checkbutton(self.root, text="复选框", command=self.Check_box, variable=self.v1)
self.Checkbutton01.grid(row=1, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=2, column=0)
def event(self):
'''按钮事件,清除复选框勾选状态'''
self.Checkbutton01.deselect()
def Check_box(self):
'''复选框事件'''
if self.v1.get() == 1:
self.w1.insert(1.0, "勾选"+'\n')
else:
self.w1.insert(1.0, "未勾选"+'\n')
文本框(Text)内容获取
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.event)
self.Button0.grid(row=0, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=1, column=0)
def event(self):
a = self.w1.get('0.0', 'end')
print(a)
下拉选择框绑定事件
def interface(self):
""""界面编写位置"""
self.value = tk.StringVar()
self.value.set('2') # 默认值
values = ['1', '2', '3', '4']
self.combobox = ttk.Combobox(
master=self.root, # 父容器
height=10, # 高度,下拉显示的条目数量
width=20, # 宽度
state='', # 设置状态 normal(可选可输入)、readonly(只可选)、 disabled(禁止输入选择)
cursor='arrow', # 鼠标移动时样式 arrow, circle, cross, plus...
font=('', 15), # 字体
textvariable=self.value, # 通过StringVar设置可改变的值
values=values, # 设置下拉框的选项
)
# 绑定事件,下拉列表框被选中时,绑定pick()函数
self.combobox.bind("<<ComboboxSelected>>", self.pick)
self.combobox.grid(padx=150)
def pick(self, *args): # 处理事件,*args表示可变参数
print('选中的数据:{}'.format(self.combobox.get()))
print('value的值:{}'.format(self.value.get()))
手动选择颜色
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="选择颜色", command=self.get_colors)
self.Button0.grid(row=0, column=1)
self.w1 = tk.Text(self.root, width=68, height=10)
self.w1.grid(row=1, column=0, columnspan=3, padx=10)
def get_colors(self):
'''手动选择颜色, 并获取颜色代码'''
color_code = colorchooser.askcolor()
# 清除text文本框
self.w1.grid_forget()
# 重新添加文本框,并添加颜色
self.w1 = tk.Text(self.root, width=68, height=10, bg=color_code[1])
self.w1.grid(row=1, column=0, columnspan=3, padx=10)
# 打印颜色代码
self.w1.insert("insert", color_code)
选择文件和文件另存
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="选择单个文件", command=self.single_file)
self.Button0.grid(row=0, column=0)
self.Button1 = tk.Button(self.root, text="选择多个文件", command=self.multiple_files)
self.Button1.grid(row=0, column=1)
self.Button2 = tk.Button(self.root, text="保存文件", command=self.save_file)
self.Button2.grid(row=0, column=2)
self.w1 = tk.Text(self.root, width=68, height=10)
self.w1.grid(row=1, column=0, columnspan=3, padx=10)
def single_file(self):
'''获取单个文件'''
file_path = filedialog.askopenfilename()
self.w1.insert("insert", file_path)
def multiple_files(self):
'''获取多个文件'''
file_path = filedialog.askopenfilenames()
self.w1.insert("insert", file_path)
def save_file(self):
'''保存文件'''
# 去掉title='保存为',标题默认名称“另存为”
file_name = filedialog.asksaveasfilename(title='保存为', filetypes=[("csv", ".csv")])
# 文件添加数据并保存
filename = file_name+'.csv'
csvfile = open(filename, 'w')
name = ['姓名', '年龄', '电话']
list1 = [
('王明', '25', '12345678'),
('张三', '18', '87654321')
]
save = pd.DataFrame(columns=name, data=list1)
save.to_csv(filename)
日期选择模块
PyInstaller打包说明:
如果使用了日期选择模块“tkcalendar.DateEntry”,打包是必须加上“--hidden-import babel.numbers”
pyinstaller --hidden-import babel.numbers myscript.py
或通过编辑.spec文件
hiddenimports=["babel.numbers"]
tkcalendar模块需要安装,命令:pip install tkcalendar
from tkcalendar import DateEntry
def interface(self):
""""界面编写位置"""
self.Label0 = tk.Label(self.root, text="日期选择")
self.Label0.grid(row=0, padx=210)
# 获取当前本地电脑日期
date = datetime.datetime.now().strftime('%Y-%m-%d')
date_print = date.split("-")
# 日期下拉选择框模块
self.date = DateEntry(self.root,
date_pattern='yyyy-mm-dd', # 指定日期格式
width=12,
year=int(date_print[0]),
month=int(date_print[1]),
day=int(date_print[2]),
background='skyblue',
foreground='white',
borderwidth=2)
self.date.grid(row=1, padx=210)
self.Button0 = tk.Button(self.root, text="运行", command=self.event)
self.Button0.grid(row=2, column=0, ipadx=10, padx=10)
self.w1 = tk.Text(self.root, width=50, height=10)
self.w1.grid(row=3, column=0)
def event(self):
# 获取日期
self.w1.insert(1.0, f"日期打印:{self.date.get()}\n")
子菜单绑定事件
from tkinter import filedialog
from tkinter import Menu
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('演示窗口')
self.root.geometry("500x200+1100+150")
# 创建主菜单实例
self.menubar = Menu(self.root)
# 显示菜单,将root根窗口的主菜单设置为menu
self.root.config(menu=self.menubar)
self.interface()
def interface(self):
""""界面编写位置"""
# 在 menubar 上设置菜单名,并关联一系列子菜单
self.menubar.add_cascade(label="文件", menu=self.papers())
self.menubar.add_cascade(label="查看", menu=self.about())
# 文本Text窗口显示
self.w1 = tk.Text(self.root, width=68, height=10)
self.w1.grid(row=1, column=0, columnspan=3, padx=10)
def papers(self):
fmenu = Menu(self.menubar, tearoff=0)
# 创建“新建”菜单项,并将其绑定到 open_file() 方法
fmenu.add_command(label="打开文件", command=self.open_file)
return fmenu
def about(self):
amenu = Menu(self.menubar, tearoff=0)
# 定义 IntVar 类型的实例变量
self.V1 = tk.IntVar()
# 创建“文件扩展名”复选框,并将其绑定到 show_extension() 方法
amenu.add_checkbutton(label="文件扩展名", variable=self.V1, command=self.show_extension)
return amenu
def open_file(self):
file_path = filedialog.askopenfilename()
self.w1.insert("insert", file_path)
def show_extension(self):
if self.V1.get() == 1:
self.w1.delete(1.0, "end")
self.w1.insert("insert", "显示文件扩展名\n")
else:
self.w1.delete(1.0, "end")
self.w1.insert("insert", "隐藏文件扩展名\n")
Tkinter使用多线程
为什么要使用多线程
以下为单线程运行
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.event)
self.Button0.grid(row=0, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=1, column=0)
def event(self):
'''按钮事件,一直循环'''
a = 0
while True:
a += 1
self.w1.insert(1.0, str(a)+'\n')
单线程下,主线程需要运行窗口,如果这个时候点击“确定”按钮,主线程就会去执行event方法,那界面就会出现“无响应”状态,如果要界面正常显示,那我们就需要用到多线程(threading)

threading语法:
```threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)```
- group:必须为None,与ThreadGroup类相关,一般不使用。
- target:目标函数
- name:线程名,默认Thread-x(x从1开始)
- args:为目标函数传递实参、元组
- kwargs:为目标函数传递关键字参数、字典
- daemon:用来设置线程是否随主线程退出而退出(当daemon设置False时,线程不会随主线程退出而退出,主线程会一直等着子线程执行完。当daemon设置True时,线程会随主线程退出而退出,主线程结束其他的子线程会强制退出)
多线程,完整代码
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定", command=self.start)
self.Button0.grid(row=0, column=0)
self.w1 = tk.Text(self.root, width=80, height=10)
self.w1.grid(row=1, column=0)
def event(self):
'''按钮事件,一直循环'''
a = 0
while True:
a += 1
self.w1.insert(1.0, str(a)+'\n')
print(a)
def start(self):
T1 = threading.Thread(name='t1', target=self.event, daemon=True) # 子线程
T1.start() # 启动

Tkinter多线程暂停和继续
import tkinter as tk
import threading
from time import sleep
event = threading.Event()
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('演示窗口')
self.root.geometry("500x200+1100+150")
self.interface()
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="启动", command=self.start)
self.Button0.grid(row=0, column=0)
self.Button1 = tk.Button(self.root, text="暂停", command=self.stop)
self.Button1.grid(row=0, column=1)
self.Button2 = tk.Button(self.root, text="继续", command=self.conti)
self.Button2.grid(row=0, column=2)
self.w1 = tk.Text(self.root, width=70, height=10)
self.w1.grid(row=1, column=0, columnspan=3)
def event(self):
'''按钮事件,一直循环'''
while True:
sleep(1)
event.wait()
self.w1.insert(1.0, '运行中'+'\n')
def start(self):
event.set()
T1 = threading.Thread(target=self.event, daemon=True)
T1.start()
def stop(self):
event.clear()
self.w1.insert(1.0, '暂停'+'\n')
def conti(self):
event.set()
self.w1.insert(1.0, '继续'+'\n')
Tkinter文件之间的调用
准备工作

a.py ---界面+线程
one/b.py ---业务逻辑
以上.py在不同文件夹下
方法
# a.py 文件
import tkinter as tk
import threading
import sys
# sys.path.append(r"./one")
from one.b import main
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title('演示窗口')
self.root.geometry("500x260+1100+150")
self.interface()
def interface(self):
""""界面编写位置"""
self.Button0 = tk.Button(self.root, text="确定执行", command=self.start, bg="#7bbfea")
self.Button0.grid(row=0, column=1, pady=10)
self.entry00 = tk.StringVar()
self.entry00.set("")
self.entry0 = tk.Entry(self.root, textvariable=self.entry00)
self.entry0.grid(row=1, column=1, pady=15)
self.w1 = tk.Text(self.root, width=50, height=8)
self.w1.grid(row=2, column=0, columnspan=3, padx=60)
def start(self):
T1 = threading.Thread(name='t1', target=main, args=(self.entry00.get(), self.w1), daemon=True)
T1.start()
if __name__ == '__main__':
a = GUI()
a.root.mainloop()
# b.py 文件
import time
def main(a, w1):
try:
x = 1
while True:
y = int(a)+x
w1.insert(1.0, str(y)+'\n')
time.sleep(1)
x += 1
except Exception:
w1.insert(1.0, '请输入数字\n')

============================= 提升自己 ==========================
进群交流、获取更多干货, 请关注微信公众号:

> > > 咨询交流、进群,请加微信,备注来意:sanshu1318 (←点击获取二维码)
> > > 学习路线+测试实用干货精选汇总:
https://www.cnblogs.com/upstudy/p/15859768.html
> > > 【自动化测试实战】python+requests+Pytest+Excel+Allure,测试都在学的热门技术:
https://www.cnblogs.com/upstudy/p/15921045.html
> > > 【热门测试技术,建议收藏备用】项目实战、简历、笔试题、面试题、职业规划:
https://www.cnblogs.com/upstudy/p/15901367.html
> > > 声明:如有侵权,请联系删除。
============================= 升职加薪 ==========================
更多干货,正在挤时间不断更新中,敬请关注+期待。
进群交流、获取更多干货, 请关注微信公众号:

> > > 咨询交流、进群,请加微信,备注来意:sanshu1318 (←点击获取二维码)
> > > 学习路线+测试实用干货精选汇总:
https://www.cnblogs.com/upstudy/p/15859768.html
> > > 【自动化测试实战】python+requests+Pytest+Excel+Allure,测试都在学的热门技术:
https://www.cnblogs.com/upstudy/p/15921045.html
> > > 【热门测试技术,建议收藏备用】项目实战、简历、笔试题、面试题、职业规划:
https://www.cnblogs.com/upstudy/p/15901367.html
> > > 声明:如有侵权,请联系删除。
============================= 升职加薪 ==========================
更多干货,正在挤时间不断更新中,敬请关注+期待。
浙公网安备 33010602011771号