在日常办公、测试或数据报告中,你是否也曾被批量生成格式化的Excel文件所困扰?手动复制粘贴不仅效率低下,还极易出错。本文将深入探讨如何利用Python,打造一个强大的Excel模板自动化填充工具,实现数据与模板的智能结合,一键生成上百份标准化报告,彻底解放你的双手。

一、工具概览:Python驱动的Excel自动化利器

我们构建的这个工具,其核心是一个基于Python的脚本,它能够读取一个预设的Excel模板文件和一个包含多行数据的信息文件。工具的核心任务是:为信息文件中的每一行数据,在输出文件中创建一个独立的工作表(Sheet),并将模板中特定的占位符(如{姓名}{部门})替换为对应行的实际数据。最终,所有个性化的工作表会被整合到一个新的Excel文件中。

例如,信息表中有10个人的信息,工具就会生成一个包含10个工作表的Excel文件,每个工作表都以对应人员的姓名命名,并填充了其专属信息。这对于批量生成测试用例、员工报告、客户合同等场景极具价值。

工具界面简洁直观:

用户只需导入模板文件和信息文件,选择输出路径,点击“生成”按钮即可。下面,我们来看看实现这一功能所需的核心技术与关键细节。掌握这些技巧,你不仅能使用现成工具,更能理解其背后的原理,甚至进行定制化开发。

二、核心技术解析:Pandas与Openpyxl的强强联合

本工具主要依赖Python的两个强大库:Pandas用于高效的数据读取和处理,Openpyxl用于精细化的Excel文件读写和格式控制。这种组合兼顾了数据处理的速度和对Excel文件格式的完全控制力。

1. 数据读取与基础操作

使用Pandas的read_excel函数可以轻松将Excel文件加载为DataFrame,这是进行所有数据操作的基础。

# 读取Excel文件
df = pd.read_excel('文件.xlsx', sheet_name='Sheet1')

遍历数据行是核心操作之一。Pandas的iterrows()方法返回一个迭代器,方便我们逐行处理数据。

import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'c']})
for idx,row in df.iterrows():
    print(f"索引:{idx},行数据:{row['A']},{row['B']}")

有时我们只关心行数据,不关心索引,可以使用下划线_忽略索引变量:

for _,row in df.iterrows():
    print(f"行数据:{row['A']},{row['B']}")

精确定位和修改DataFrame中的值,.loc[]方法是首选。它通过行标签和列标签进行访问,语义清晰。

import pandas as pd
df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6],
    'C': [7, 8, 9]
}, index=['row1', 'row2', 'row3'])
# 使用.loc - 基于标签
print(df.loc['row2', 'B'])  # 输出: 5
# 使用.iloc - 基于位置(整数索引)
print(df.iloc[1, 1])       # 输出: 5 (第2行第2列)

获取DataFrame的列名同样简单:

import pandas as pd
# 创建一个DataFrame
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五'],
    '年龄': [25, 30, 28],
    '城市': ['北京', '上海', '广州']
})
# .columns 返回列名
print(df.columns)
# 输出: Index(['姓名', '年龄', '城市'], dtype='object')
print(type(df.columns))  # 

2. 工作表名称的规范化处理

Excel对工作表名称有严格限制(如不能超过31字符、不能包含[]:*?/\等字符)。直接使用数据中的字段(如姓名)作为Sheet名可能存在风险。因此,我们需要一个清洗函数clean_sheet_name()来确保名称合规。

该函数clean_sheet_name()的核心逻辑包括:

  • 长度截断:使用切片操作确保名称不超过31个字符。
  • 空格处理:使用strip()移除首尾空格,并检查是否为空。
  • 非法字符替换:将Excel不允许的字符替换为下划线。

字符串切片操作示例:

name = 'Hello world'
print(name[:3])
print(name[0:3])
print(name[slice(0,3)])

使用strip()处理空格:

name = '  12Hello world34  '
print(f"移除首尾空格:",name.strip())
print(f"移除左边空格:",name.lstrip())
print(f"移除右边空格:",name.rstrip())
# 先移除首端的空白再移除数字
print(f"移除指定字符:",name.lstrip().strip('12'))

这里涉及一个Python的布尔逻辑:空字符串在条件判断中为False。因此if not name.strip()可以有效地检查去除空格后是否为空。

# 确保名称不为空
# 方法1
    if not name.strip():
        name = "Sheet"
# 方法2
if name.strip() == "":
    name = "Sheet"
# 方法3
if len(name.strip()) == 0:
    name = "Sheet"

完整的清洗函数还需要处理可能的缺失值(NaN),可以使用Pandas的pd.isna()pd.isna()进行判断。

import pandas as pd
def clean_sheet_name(name, max_length=31):
    """
    清理sheet名称,确保符合Excel命名规则
    参数:
    name: 原始sheet名称
    max_length: 最大长度限制(Excel限制为31个字符)
    """
    if pd.isna(name):
        name = "Sheet"
    else:
        name = str(name)
    # 移除非法字符
    illegal_chars = [':', '\\', '/', '?', '*', '[', ']']
    for char in illegal_chars:
        name = name.replace(char, '')
    # 截断到最大长度
    if len(name) > max_length:
        name = name[:max_length]
    # 确保名称不为空
    if not name.strip():
        name = "Sheet"
    return name
# 测试各种情况
test_cases = [
    None,                     # 空值
    "销售数据:2024年/第一季度",  # 包含非法字符
    "这是一个非常非常非常长的Excel工作表名称超过了31个字符llll",  # 超长
    "   ",                    # 只有空白
    "正常名称",               # 正常情况
]
for test in test_cases:
    print(f"原始Sheet名称:{test} -> 清理后名称:{clean_sheet_name(test)}")
[AFFILIATE_SLOT_1]

三、核心实现:模板填充与文件生成

这是工具的“心脏”部分。我们使用pd.ExcelWriter作为上下文管理器来创建和写入Excel文件,这是最推荐的方式,它能确保文件被正确关闭。

# 等价写法(不推荐,因为需要手动关闭)
writer = pd.ExcelWriter(output_file, engine='openpyxl')
try:
    # 写入操作
    total_rows = len(info_df)
    # ...
finally:
    writer.save()  # 必须手动保存
    writer.close()  # 必须手动关闭
# 使用 with 语句(推荐)
# 自动处理资源的打开和关闭,即使出错也会正确关闭
with pd.ExcelWriter(...) as writer:
    # 写入操作
    pass
# 退出 with 块时自动调用 writer.save() 和 writer.close()

参数output_file指定输出路径,engine='openpyxl'指定使用openpyxl引擎。writerwriter就是这个写入器对象。

核心的替换逻辑经历了优化。最初的朴素方法是多层循环遍历模板每个单元格,与信息表每列比对,效率较低。优化后的策略是:

  1. 提取占位符:一次性扫描模板,使用正则表达式提取所有形如{xxx}的占位符,并去重。
  2. 映射替换:遍历信息表每一行数据时,只需将去重后的占位符列表与当前行数据的列名进行匹配,一致的则进行替换。
  3. 性能提升:通过占位符去重减少了不必要的比对次数;在可能的地方使用Pandas的向量化操作;采用流式处理和模板复用来控制内存使用峰值。

关键替换代码逻辑如下:

 # 4. 为信息表中的每一行生成一个sheet
            for index, row in info_df.iterrows():
                # 获取编号(用于sheet名称)
                sheet_name = clean_sheet_name(row.get('编号'))
                print(f"正在处理第 {index + 1}/{total_rows} 行: {sheet_name}")
                # 复制模板
                test_case_df = template_df.copy()
                # 5. 替换模板中的占位符
                for col in test_case_df.columns:
                    for idx in test_case_df.index:
                        cell_value = clean_value(test_case_df.loc[idx, col])
                        # 提取实际存在的占位符-- 非贪婪匹配,匹配 { 和 } 之间的所有内容
                        placeholders = re.findall(r'\{(.*?)\}', cell_value)
                        if placeholders:  # 只有存在占位符时才处理
                            for placeholder in set(placeholders):  # 去重
                                if placeholder in info_df.columns:
                                    replacement = clean_value(row.get(placeholder, ""))
                                    cell_value = cell_value.replace(f"{{{placeholder}}}", replacement)
                        test_case_df.loc[idx, col] = cell_value

生成文件后,我们还可以利用openpyxl自动调整列宽,让报告更加美观:

def auto_adjust_column_width(worksheet, df):
    """
    自动调整Excel列的宽度以适应内容
    参数:
    worksheet: openpyxl的worksheet对象
    df: 对应的DataFrame
    """
    try:
        # 遍历DataFrame的每一列
        for column in df:
            # 获取列索引(从1开始)
            column_letter = openpyxl.utils.get_column_letter(df.columns.get_loc(column) + 1)
            # 计算列的最大宽度
            # 考虑列名本身的宽度
            max_length = max(
                len(str(column)),  # 列名宽度
                df[column].astype(str).map(len).max()  # 该列数据最大宽度
            )
            # 添加一些边距(增加20%的宽度或至少5个字符)
            adjusted_width = max_length * 1.2 + 5
            # 设置列宽,限制在5到50之间
            adjusted_width = max(5, min(adjusted_width, 50))
            # 应用列宽
            worksheet.column_dimensions[column_letter].width = adjusted_width

最终生成的效果如下图所示,每个人拥有独立且格式规范的工作表:

四、常见问题与进阶技巧

1. 占位符格式的严格性

最初版本的工具可能要求占位符必须是简单的{字段名}形式。如果包含空格或冒号等,如{姓名:},正则表达式可能无法正确匹配。这要求我们在设计模板时保持占位符的简洁性,或者修改正则表达式模式以兼容更复杂的格式。

原始正则可能只匹配字母、数字和下划线:

# 提取实际存在的占位符
placeholders = re.findall(r'\{(\w+)\}', cell_value)

为了兼容空格和中文,可以调整模式:

import re
# 示例1
cell_value = "Hello {name}, your score is {score_2023}"
placeholders = re.findall(r'\{(\w+)\}', cell_value)
# 结果: ['name', 'score_2023']
# 示例2 - 不匹配的情况
cell_value = "No placeholders here"
placeholders = re.findall(r'\{(\w+)\}', cell_value)
# 结果: []

技术延伸:这种基于模板和数据的生成思想,在JavaScript(如Node.js的xlsx库)、Go(如excelize库)乃至C++中都有对应的实现。选择Python是因为其生态丰富、开发效率高,但对于追求极致性能或需要集成到特定系统的场景,TypeScriptGo也是优秀的选择。

2. 字符串格式化中的花括号转义

在Python的f-string或.format()中,花括号{}有特殊含义。如果模板中需要保留字面意义上的花括号,需要进行转义,使用双花括号{{}}

name = "张三"
age = 25
print(f"{name}")           # 张三
print(f"{{name}}")         # {name}
print(f"{{{name}}}")       # {张三}
print(f"{{{{{name}}}}}")   # {{张三}}
[AFFILIATE_SLOT_2]

五、从脚本到可执行文件:打包与分发

为了让不懂Python的同事也能方便地使用这个工具,我们需要将其打包成一个独立的可执行文件(.exe)。PyInstaller是完成这项任务的最佳工具之一。

首先安装PyInstaller:

pip install pyinstaller

创建一个简单的打包脚本(如build.specbuild_exe.py)可以更精细地控制打包过程,例如添加数据文件、设置图标等。

# build_exe.py - 打包脚本
import PyInstaller.__main__
import os
import shutil
import sys
def build_executable():
    """构建可执行文件"""
    # 主程序入口文件
    main_script = "panel.py"  # 假设您的主程序是 panel.py
    # PyInstaller 配置参数
    args = [
        main_script,                # 主脚本
        '--name=TestCaseGenerator', # 程序名称
        '--onefile',                # 打包成单个exe文件
        '--windowed',               # 如果是GUI程序(控制台程序去掉这个)
        '--icon=icon.ico',         # 图标文件(可选)
        '--add-data=templates;templates',  # 包含模板文件夹
        '--add-data=*.xlsx;.',     # 包含Excel文件
        '--hidden-import=pandas',
        '--hidden-import=openpyxl',
        '--hidden-import=numpy',
        '--clean',                  # 清理临时文件
    ]
    # 运行打包
    PyInstaller.__main__.run(args)
    print("打包完成!可执行文件在 dist/TestCaseGenerator.exe")
if __name__ == "__main__":
    build_executable()

然后执行打包命令:

# 1. 将panel.py修改为可执行入口(如果需要)
# 2. 执行打包命令
pyinstaller --onefile --name=TestTool panel.py

打包成功后,你会得到一个独立的.exe文件,可以在没有Python环境的Windows电脑上直接运行。

实践建议:打包时注意将模板文件、配置文件等作为“数据文件”包含进去,或者让程序在运行时从指定目录读取,以增强工具的灵活性。

六、总结与源码

通过本文的探讨,我们完成了一个从需求分析、技术选型(Pandas+Openpyxl)、核心功能实现(数据读取、模板替换、文件写入)、问题解决(名称规范、占位符匹配)到最终打包分发的完整项目流程。这个Excel模板填充工具的核心价值在于:

  • 提升效率:将重复的手工劳动转化为秒级的自动化操作。
  • 保证准确:杜绝人工复制粘贴可能带来的错误。
  • 标准化输出:确保每一份生成的文件都遵循统一的格式规范。
  • 灵活可扩展:通过修改模板和信息表,可以轻松适配各种报告生成场景。

理解其原理后,你可以轻松地将其改造成批量生成Word文档、PDF报告或邮件的工具,自动化思维是相通的。以下是两个核心模块的源码参考,它们构成了这个工具的骨架:

主逻辑模块 (CaseOutput.py):

import pandas as pd
import openpyxl
import warnings
import re  # 正则表达式模块,用于提取占位符
warnings.filterwarnings('ignore')
def clean_sheet_name(name, max_length=31):
    """
    清理sheet名称,确保符合Excel命名规则
    参数:
    name: 原始sheet名称
    max_length: 最大长度限制(Excel限制为31个字符)
    """
    if pd.isna(name):
        name = "Sheet"
    else:
        name = str(name)
    # 移除非法字符
    illegal_chars = [':', '\\', '/', '?', '*', '[', ']']
    for char in illegal_chars:
        name = name.replace(char, '')
    # 截断到最大长度
    if len(name) > max_length:
        name = name[:max_length]
    # 确保名称不为空
    if not name.strip():
        name = "Sheet"
    return name
def clean_value(value):
    """
    清理单元格值,处理NaN和None
    参数:
    value: 原始值
    返回: 清理后的字符串
    """
    if pd.isna(value):
        return ""
    return str(value)
def auto_adjust_column_width(worksheet, df):
    """
    自动调整Excel列的宽度以适应内容
    参数:
    worksheet: openpyxl的worksheet对象
    df: 对应的DataFrame
    """
    try:
        # 遍历DataFrame的每一列
        for column in df:
            # 获取列索引(从1开始)
            column_letter = openpyxl.utils.get_column_letter(df.columns.get_loc(column) + 1)
            # 计算列的最大宽度
            # 考虑列名本身的宽度
            max_length = max(
                len(str(column)),  # 列名宽度
                df[column].astype(str).map(len).max()  # 该列数据最大宽度
            )
            # 添加一些边距(增加20%的宽度或至少5个字符)
            adjusted_width = max_length * 1.2 + 5
            # 设置列宽,限制在5到50之间
            adjusted_width = max(5, min(adjusted_width, 50))
            # 应用列宽
            worksheet.column_dimensions[column_letter].width = adjusted_width
    except Exception as e:
        print(f"自动调整列宽时发生错误: {e}")
        # 如果出错,设置默认列宽
        for column in df.columns:
            column_letter = openpyxl.utils.get_column_letter(df.columns.get_loc(column) + 1)
            worksheet.column_dimensions[column_letter].width = 20
def generate_test_cases_from_files(template_file, info_file, output_file):
    """
    处理文件生成测试用例
    参数:
    template_file: 模板文件路径
    info_file: 信息表文件路径
    output_file: 输出文件路径
    返回: 生成的测试用例数量
    """
    try:
        print("正在读取模板文件...")
        # 1. 读取模板文件,保持空单元格为空字符串
        template_df = pd.read_excel(
            template_file,
            dtype=str,  # 所有列都读取为字符串类型
            keep_default_na=False
        )
        print("正在读取信息表...")
        # 2. 读取信息表,保持空单元格为空字符串
        info_df = pd.read_excel(
            info_file,
            dtype=str,
            keep_default_na=False
        )
        print("正在生成Excel文件...")
        # 3. 创建Excel写入对象
        with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
            total_rows = len(info_df)
            # 4. 为信息表中的每一行生成一个sheet
            for index, row in info_df.iterrows():
                # 获取编号(用于sheet名称)
                sheet_name = clean_sheet_name(row.get('编号'))
                print(f"正在处理第 {index + 1}/{total_rows} 行: {sheet_name}")
                # 复制模板
                test_case_df = template_df.copy()
                # 5. 替换模板中的占位符
                for col in test_case_df.columns:
                    for idx in test_case_df.index:
                        cell_value = clean_value(test_case_df.loc[idx, col])
                        # 提取实际存在的占位符-- 非贪婪匹配,匹配 { 和 } 之间的所有内容
                        placeholders = re.findall(r'\{(.*?)\}', cell_value)
                        if placeholders:  # 只有存在占位符时才处理
                            for placeholder in set(placeholders):  # 去重
                                if placeholder in info_df.columns:
                                    replacement = clean_value(row.get(placeholder, ""))
                                    cell_value = cell_value.replace(f"{{{placeholder}}}", replacement)
                        test_case_df.loc[idx, col] = cell_value
                # 6. 将当前指示灯的测试用例保存到sheet
                test_case_df.to_excel(
                    writer,
                    sheet_name=sheet_name,
                    index=False
                )
                # 获取当前sheet的worksheet对象
                worksheet = writer.sheets[sheet_name]
                # 调用自动调整列宽函数
                auto_adjust_column_width(worksheet, test_case_df)
                print(f"  已自动调整 {sheet_name} 的列宽")
        print(f"Excel文件生成完成,共生成 {total_rows} 个测试用例")
        return total_rows
    except KeyError as e:
        raise Exception(f"Excel文件中缺少必要的列: {e}")
    except PermissionError as e:
        raise Exception(f"文件访问权限错误,请关闭Excel文件后重试: {e}")
    except Exception as e:
        raise Exception(f"文件处理错误: {e}")

GUI界面模块 (panel.py):

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
from CaseOutput import generate_test_cases_from_files
class TestCaseGeneratorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("测试用例生成器")
        self.root.geometry("650x350")
        # 初始化文件路径
        self.template_file = ""
        self.info_file = ""
        self.output_file = ""
        # 设置样式
        self.setup_styles()
        # 创建界面
        self.create_widgets()
    def setup_styles(self):
        """设置界面样式"""
        style = ttk.Style()
        style.theme_use('clam')
        # 自定义大按钮样式
        style.configure(
            'Large.TButton',
            font=('Microsoft YaHei', 11, 'bold'),
            padding=(10, 8),
            anchor='center',
            relief='raised',
            width=15
        )
        # 标签样式
        style.configure(
            'File.TLabel',
            font=('Microsoft YaHei', 10),
            padding=(5, 2)
        )
        # 标签框样式
        style.configure(
            'TLabelframe',
            font=('Microsoft YaHei', 11, 'bold'),
            padding=10
        )
    def create_widgets(self):
        """创建界面组件"""
        # 主容器
        main_container = ttk.Frame(self.root, padding=20)
        main_container.pack(fill='both', expand=True)
        # 标题
        title_label = tk.Label(
            main_container,
            text="测试用例生成工具",
            font=("Microsoft YaHei", 18, "bold"),
            fg="#2c3e50",
            bg="#ecf0f1",
            pady=15
        )
        title_label.pack(fill='x', pady=(0, 20))
        # 创建内容框架
        content_frame = ttk.Frame(main_container)
        content_frame.pack(fill='both', expand=True)
        # 文件选择区域
        self.create_file_selection(content_frame)
        # 按钮区域
        self.create_button_area(content_frame)
    def create_file_selection(self, parent):
        """创建文件选择区域"""
        # 模板文件选择区域
        template_frame = ttk.LabelFrame(parent, text="1. 选择模板文件")
        template_frame.pack(fill="x", padx=10, pady=10)
        template_inner = ttk.Frame(template_frame)
        template_inner.pack(fill='x', padx=5, pady=8)
        self.template_label = ttk.Label(
            template_inner,
            text="未选择文件",
            foreground="gray",
            style='File.TLabel',
            width=50,
            anchor='w'
        )
        self.template_label.pack(side="left", fill='x', expand=True, padx=(10, 5))
        template_btn = ttk.Button(
            template_inner,
            text="选择模板文件",
            command=self.select_template_file,
            style='Large.TButton'
        )
        template_btn.pack(side="right", padx=5)
        # 信息表文件选择区域
        info_frame = ttk.LabelFrame(parent, text="2. 选择信息表")
        info_frame.pack(fill="x", padx=10, pady=10)
        info_inner = ttk.Frame(info_frame)
        info_inner.pack(fill='x', padx=5, pady=8)
        self.info_label = ttk.Label(
            info_inner,
            text="未选择文件",
            foreground="gray",
            style='File.TLabel',
            width=50,
            anchor='w'
        )
        self.info_label.pack(side="left", fill='x', expand=True, padx=(10, 5))
        info_btn = ttk.Button(
            info_inner,
            text="选择信息表",
            command=self.select_info_file,
            style='Large.TButton'
        )
        info_btn.pack(side="right", padx=5)
        # 输出文件选择区域
        output_frame = ttk.LabelFrame(parent, text="3. 选择输出文件位置")
        output_frame.pack(fill="x", padx=10, pady=10)
        output_inner = ttk.Frame(output_frame)
        output_inner.pack(fill='x', padx=5, pady=8)
        self.output_label = ttk.Label(
            output_inner,
            text="未选择位置",
            foreground="gray",
            style='File.TLabel',
            width=50,
            anchor='w'
        )
        self.output_label.pack(side="left", fill='x', expand=True, padx=(10, 5))
        output_btn = ttk.Button(
            output_inner,
            text="选择输出位置",
            command=self.select_output_file,
            style='Large.TButton'
        )
        output_btn.pack(side="right", padx=5)
    def create_button_area(self, parent):
        """创建按钮区域"""
        button_frame = ttk.Frame(parent)
        button_frame.pack(pady=30)
        # 使用tk.Button获得更多控制
        self.generate_btn = tk.Button(
            button_frame,
            text="生成测试用例",
            font=('Microsoft YaHei', 12, 'bold'),
            command=self.generate_test_cases,
            state="disabled",
            bg="#27ae60",
            fg="white",
            activebackground="#229954",
            activeforeground="white",
            relief="raised",
            bd=2,
            padx=30,
            pady=12,
            cursor="hand2",
            width=15
        )
        self.generate_btn.pack(side="left", padx=20)
        self.clear_btn = tk.Button(
            button_frame,
            text="清空选择",
            font=('Microsoft YaHei', 12, 'bold'),
            command=self.clear_selection,
            bg="#e74c3c",
            fg="white",
            activebackground="#c0392b",
            activeforeground="white",
            relief="raised",
            bd=2,
            padx=30,
            pady=12,
            cursor="hand2",
            width=12
        )
        self.clear_btn.pack(side="left", padx=20)
    def select_template_file(self):
        """选择模板文件"""
        file_path = filedialog.askopenfilename(
            title="选择模板文件",
            filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
        )
        if file_path:
            self.template_file = file_path
            file_name = os.path.basename(file_path)
            self.template_label.config(text=file_name, foreground="black")
            self.check_generate_button()
    def select_info_file(self):
        """选择信息表文件"""
        file_path = filedialog.askopenfilename(
            title="选择信息表",
            filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
        )
        if file_path:
            self.info_file = file_path
            file_name = os.path.basename(file_path)
            self.info_label.config(text=file_name, foreground="black")
            self.check_generate_button()
    def select_output_file(self):
        """选择输出文件位置"""
        file_path = filedialog.asksaveasfilename(
            title="保存测试用例文件",
            defaultextension=".xlsx",
            filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
        )
        if file_path:
            self.output_file = file_path
            file_name = os.path.basename(file_path)
            self.output_label.config(text=file_name, foreground="black")
            self.check_generate_button()
    def check_generate_button(self):
        """检查是否启用生成按钮"""
        if self.template_file and self.info_file and self.output_file:
            self.generate_btn.config(state="normal", bg="#27ae60")
        else:
            self.generate_btn.config(state="disabled", bg="#95a5a6")
    def clear_selection(self):
        """清空所有选择"""
        self.template_file = ""
        self.info_file = ""
        self.output_file = ""
        self.template_label.config(text="未选择文件", foreground="gray")
        self.info_label.config(text="未选择文件", foreground="gray")
        self.output_label.config(text="未选择位置", foreground="gray")
        self.generate_btn.config(state="disabled", bg="#95a5a6")
    def generate_test_cases(self):
        """生成测试用例"""
        try:
            # 检查文件是否存在
            if not os.path.exists(self.template_file):
                messagebox.showerror("错误", f"模板文件不存在: {self.template_file}")
                return
            if not os.path.exists(self.info_file):
                messagebox.showerror("错误", f"信息表文件不存在: {self.info_file}")
                return
            # 调用生成函数
            result = generate_test_cases_from_files(
                self.template_file,
                self.info_file,
                self.output_file
            )
            if result:
                messagebox.showinfo(
                    "生成成功",
                    f"测试用例生成成功!\n\n"
                    f"保存位置: {self.output_file}\n"
                    f"共生成 {result} 个测试用例"
                )
            else:
                messagebox.showerror("错误", "生成失败,请检查文件格式")
        except Exception as e:
            messagebox.showerror("错误", f"生成过程中发生错误:\n{str(e)}")
def main():
    root = tk.Tk()
    # 设置窗口居中
    window_width = 650
    window_height = 400
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    x = (screen_width - window_width) // 2
    y = (screen_height - window_height) // 2
    root.geometry(f"{window_width}x{window_height}+{x}+{y}")
    # 创建应用
    app = TestCaseGeneratorGUI(root)
    # 运行主循环
    root.mainloop()
if __name__ == "__main__":
    main()

希望这个详细的解读和完整的项目实践,能帮助你不仅学会使用一个工具,更能掌握用Python解决实际办公自动化问题的思路与方法。自动化不是目的,而是为了让我们有更多时间专注于更有创造性的工作。

---

相关课程

根据本文内容,精选以下优质课程: