用Java实现测试用例的模板化输出

引言:从枯燥重复中寻找突破口

因为工作中总能看到测试工程师们打开Excel文件,一行行地复制测试用例的标题和步骤,再切换到Word模板中粘贴、调整格式,然后才能开始真正的测试工作。这个过程不仅耗时(每个测试集平均需要15-20分钟准备文档),而且容易出错(可能粘错行、漏步骤),更糟糕的是,格式不统一的问题时有发生。为了解决这个问题,我开发了一个小的工具。

操作页面:

image

工具主要特性:

  • 简洁的Swing图形界面,非技术人员也能轻松使用

  • 选择Excel文件后,一键生成符合公司模板的Word文档

  • 保持原有的所有格式和样式

核心思路与技术选型

为了实现这个工具,我设计了如下的技术架构:
deepseek_mermaid_20251205_6bad4d

技术栈的考量:

1. Apache POI - 用于处理Excel文件

成熟稳定,支持.xls和.xlsx格式

提供完整的API操作单元格、行、列

2. Apache POI-TL (poi-template) - 处理Word生成

这是我调研后的关键选择(稍后会详细说明为什么)

基于POI,但提供了更友好的模板语法

支持循环、条件判断等高级功能

3.Swing - 构建桌面界面

Java标准库,无需额外依赖

虽然不够现代,但足够实现简单功能

4. Maven - 项目构建和依赖管理

方便打包成可执行的FatJar

实战踩坑记:四个遇到的问题与解决方案

问题一:如何平衡灵活性与规范性?

问题描述:最初我尝试直接用POI API从头创建Word文档,很快就陷入了困境:

因为我对word的语法不是很熟,而且因为一个简单的工具再去了解这些,显然有些浪费时间。

解决方案:采用 "模板+数据" 模式。word直接拿来测试正在用的word转变为ftl,修改为一个基础的模板,一些固定的数据写死,灵活的数据做为变量,由测试人员写入。

问题二:Excel中的结构如何准确转换?

问题描述:我们的测试用例Excel表格比较简单,第一行是表头,第二行开始就是数据

解决方案:简单的逐行读取这些结构信息。创建TestCaseVO 对应excel表格中的数据。

问题三:如何让非技术的同事正常使用

问题描述:测试的同事常常有测试任务,每次写完用例都发给我让我生成word,明显是不现实的,而且给我增加了工作量。

解决方案:用Swing构建界面,让同事可以直接在本地选择需要生成word的excel文件,录入版本内容,版本号等一些变量,然后点击生成按钮,可以在excel文件所在的位置,生成对应的word文档。

问题四:直接使用jar包报错

问题描述:本地代码Swing构建界面正常,但是当达成jar包,直接使用时会找不到ftl模板文件

解决方案:原来代码中是直接使用src/main/sources/ftl/doc.ftl这个文件的,用configuration.setClassForTemplateLoading(this.getClass(), "/");加载目录,template = configuration.getTemplate("ftl/testDocument.ftl");将路径前面的src/main/sources/删掉。

private static Configuration configuration;

    static {
        configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
    }

工具带来的改变

量化效果:

  • 文档准备时间:从平均15分钟减少到10秒

  • 错误率:从约5%降低到0%

后续改进

这个工具可以改进的地方:

  • 可以根据不同的需求选择对应的模板生成,不再是固定的jar包中写死的模板

  • 集成截图管理:将截图按规则放入对应的文件夹内后,可以自动添加到文档中

  • Web化版本:方便团队协作和权限管理

附录

部分代码片段

桌面界面代码
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;

public class WindowsFileChooserWithInputs {
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Excel文件选择器 - 带输入框");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(600, 500);
            
            // 创建主面板
            JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
            mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
            
            // 创建顶部面板(文件选择和结果展示)
            JPanel topPanel = new JPanel(new GridBagLayout());
            topPanel.setBorder(BorderFactory.createTitledBorder("文件选择区域"));
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.insets = new Insets(5, 5, 5, 5);
            gbc.fill = GridBagConstraints.HORIZONTAL;
            
            // 文件选择按钮
            JButton openButton = new JButton("选择Excel文件");
            gbc.gridx = 0; gbc.gridy = 0;
            gbc.gridwidth = 2;
            topPanel.add(openButton, gbc);
            
            // 结果展示区域
            JTextArea resultArea = new JTextArea(6, 40);
            resultArea.setEditable(false);
            resultArea.setLineWrap(true);
            resultArea.setWrapStyleWord(true);
            JScrollPane scrollPane = new JScrollPane(resultArea);
            gbc.gridx = 0; gbc.gridy = 1;
            gbc.gridwidth = 2;
            gbc.fill = GridBagConstraints.BOTH;
            gbc.weightx = 1.0;
            topPanel.add(scrollPane, gbc);
            
            // 创建中间面板(三个输入框)
            JPanel inputPanel = new JPanel(new GridBagLayout());
            inputPanel.setBorder(BorderFactory.createTitledBorder("参数设置"));
            GridBagConstraints gbcInput = new GridBagConstraints();
            gbcInput.insets = new Insets(8, 8, 8, 8);
            gbcInput.fill = GridBagConstraints.HORIZONTAL;
            
            // 输入框1
            gbcInput.gridx = 0; gbcInput.gridy = 0;
            inputPanel.add(new JLabel("工作表名称:"), gbcInput);
            
            JTextField sheetNameField = new JTextField(25);
            sheetNameField.setToolTipText("输入要处理的Excel工作表名称");
            gbcInput.gridx = 1; gbcInput.gridy = 0;
            inputPanel.add(sheetNameField, gbcInput);
            
            // 输入框2
            gbcInput.gridx = 0; gbcInput.gridy = 1;
            inputPanel.add(new JLabel("起始行:"), gbcInput);
            
            JTextField startRowField = new JTextField(25);
            startRowField.setToolTipText("输入数据起始行号(从0开始)");
            gbcInput.gridx = 1; gbcInput.gridy = 1;
            inputPanel.add(startRowField, gbcInput);
            
            // 输入框3
            gbcInput.gridx = 0; gbcInput.gridy = 2;
            inputPanel.add(new JLabel("输出文件名:"), gbcInput);
            
            JTextField outputNameField = new JTextField(25);
            outputNameField.setToolTipText("输入处理后的输出文件名");
            gbcInput.gridx = 1; gbcInput.gridy = 2;
            inputPanel.add(outputNameField, gbcInput);
            
            // 创建底部按钮面板
            JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10));
            
            // 处理按钮
            JButton processButton = new JButton("处理文件");
            processButton.setEnabled(false);
            
            // 重置按钮
            JButton resetButton = new JButton("重置");
            
            // 文件选择按钮事件
            openButton.addActionListener(e -> {
                // 调用系统文件选择器
                FileDialog fileDialog = new FileDialog(frame, "选择Excel文件", FileDialog.LOAD);
                
                // 设置文件过滤器(显示.xls和.xlsx文件)
                fileDialog.setFile("*.xls;*.xlsx");
                fileDialog.setDirectory(System.getProperty("user.home")); // 默认打开用户目录
                
                // 显示对话框(模态)
                fileDialog.setVisible(true);
                
                // 获取选择结果
                String fileName = fileDialog.getFile();
                String directory = fileDialog.getDirectory();
                
                if (fileName != null && directory != null) {
                    // 创建文件对象
                    File selectedFile = new File(directory, fileName);
                    
                    // 显示结果
                    String result = "✅ 文件选择成功!\n" +
                                   "═══════════════════════════════\n" +
                                   "📄 文件名: " + fileName + "\n" +
                                   "📁 文件目录: " + directory + "\n" +
                                   "📍 完整路径: " + selectedFile.getAbsolutePath() + "\n" +
                                   "📊 文件大小: " + formatFileSize(selectedFile.length());
                    
                    resultArea.setText(result);
                    
                    // 启用处理按钮
                    processButton.setEnabled(true);
                    
                    // 自动填充输入框示例值
                    autoFillInputFields(selectedFile, sheetNameField, startRowField, outputNameField);
                    
                    // 在实际应用中,这里可以添加处理文件的代码
                    System.out.println("已选择文件: " + selectedFile.getAbsolutePath());
                } else {
                    resultArea.setText("❌ 用户取消了选择");
                    processButton.setEnabled(false);
                }
            });
            
            // 处理按钮事件
            processButton.addActionListener(e -> {
                // 获取输入框的值
                String sheetName = sheetNameField.getText().trim();
                String startRow = startRowField.getText().trim();
                String outputName = outputNameField.getText().trim();
                
                // 验证输入
                if (sheetName.isEmpty() || startRow.isEmpty() || outputName.isEmpty()) {
                    JOptionPane.showMessageDialog(frame, 
                        "请填写所有参数!", 
                        "参数错误", 
                        JOptionPane.WARNING_MESSAGE);
                    return;
                }
                
                // 验证起始行是否为数字
                try {
                    int rowNum = Integer.parseInt(startRow);
                    if (rowNum < 0) {
                        throw new NumberFormatException();
                    }
                } catch (NumberFormatException ex) {
                    JOptionPane.showMessageDialog(frame, 
                        "起始行必须是非负整数!", 
                        "参数错误", 
                        JOptionPane.ERROR_MESSAGE);
                    return;
                }
                
                // 执行处理逻辑
                String processResult = processFileWithParams(
                    resultArea.getText(),
                    sheetName,
                    startRow,
                    outputName
                );
                
                // 显示处理结果
                JOptionPane.showMessageDialog(frame, 
                    processResult, 
                    "处理完成", 
                    JOptionPane.INFORMATION_MESSAGE);
            });
            
            // 重置按钮事件
            resetButton.addActionListener(e -> {
                // 清空所有输入框
                sheetNameField.setText("");
                startRowField.setText("");
                outputNameField.setText("");
                resultArea.setText("");
                processButton.setEnabled(false);
                
                JOptionPane.showMessageDialog(frame, 
                    "已重置所有参数", 
                    "重置完成", 
                    JOptionPane.INFORMATION_MESSAGE);
            });
            
            buttonPanel.add(processButton);
            buttonPanel.add(resetButton);
            
            // 添加到主面板
            mainPanel.add(topPanel, BorderLayout.NORTH);
            mainPanel.add(inputPanel, BorderLayout.CENTER);
            mainPanel.add(buttonPanel, BorderLayout.SOUTH);
            
            frame.add(mainPanel);
            frame.setVisible(true);
        });
    }
    
    /**
     * 根据选择的文件自动填充输入框
     */
    private static void autoFillInputFields(File file, JTextField sheetNameField, 
                                          JTextField startRowField, JTextField outputNameField) {
        String fileName = file.getName();
        String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
        
        // 设置默认工作表名称
        sheetNameField.setText("Sheet1");
        
        // 设置默认起始行
        startRowField.setText("1");
        
        // 生成默认输出文件名
        outputNameField.setText("processed_" + baseName + ".xlsx");
    }
    
    /**
     * 处理文件(带参数)
     */
    private static String processFileWithParams(String fileInfo, String sheetName, 
                                               String startRow, String outputName) {
        StringBuilder result = new StringBuilder();
        result.append("📋 处理参数汇总:\n");
        result.append("═══════════════════════════════\n");
        result.append("📄 工作表名称: ").append(sheetName).append("\n");
        result.append("📍 起始行号: ").append(startRow).append("\n");
        result.append("💾 输出文件名: ").append(outputName).append("\n");
        result.append("═══════════════════════════════\n\n");
        
        // 添加文件信息
        result.append(fileInfo).append("\n\n");
        
        // 模拟处理过程
        result.append("🔄 处理过程:\n");
        result.append("1. 正在读取Excel文件...\n");
        result.append("2. 正在定位工作表【").append(sheetName).append("】...\n");
        result.append("3. 正在从第 ").append(startRow).append(" 行开始处理...\n");
        result.append("4. 正在生成输出文件【").append(outputName).append("】...\n");
        result.append("5. ✅ 处理完成!\n");
        
        return result.toString();
    }
    
    /**
     * 处理选中的Excel文件(这里可以扩展为实际的处理逻辑)
     */
    private static void processExcelFile(File file) {
        System.out.println("开始处理Excel文件: " + file.getName());
        System.out.println("文件路径: " + file.getAbsolutePath());
        
        // 示例:检查文件扩展名
        String fileName = file.getName();
        if (fileName.toLowerCase().endsWith(".xls")) {
            System.out.println("这是旧版Excel文件 (.xls)");
        } else if (fileName.toLowerCase().endsWith(".xlsx")) {
            System.out.println("这是新版Excel文件 (.xlsx)");
        }
    }
    
    /**
     * 格式化文件大小
     */
    private static String formatFileSize(long size) {
        if (size < 1024) return size + " B";
        if (size < 1024 * 1024) return String.format("%.1f KB", size / 1024.0);
        if (size < 1024 * 1024 * 1024) return String.format("%.1f MB", size / (1024.0 * 1024));
        return String.format("%.1f GB", size / (1024.0 * 1024 * 1024));
    }
}
主要依赖
<dependency>
	<groupId>org.freemarker</groupId>
	<artifactId>freemarker</artifactId>
	<version>5.2.1</version>
</dependency>
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi</artifactId>
	<version>5.2.1</version>
</dependency>
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi-ooxml</artifactId>
	<version>5.2.1</version>
</dependency>

jar包生成以及工具使用

  1. 用idea 生成jar包 Project Structure -> Artifacts -> 添加jar -> From modules with denpendencies -> main class 选自己工程的启动class 其他的不用动 -> ok

  2. Build -> Build Artifacts -> Build 然后去out目录下找自己的jar文件

  3. 准备Excel文件:确保包含必要的列(用例ID、标题、步骤等)

  4. 运行工具:双击TestCaseConverter.jar,或者本地写一个bat文件,用java -jar test.jar 来启动,需要修改启动参数时

  5. 选择文件:点击"选择Excel"按钮

  6. 生成文档:点击"处理文件"

  7. 完成:在指定位置查看生成的Word文档

posted @ 2025-12-05 10:54  暴躁牛马  阅读(1)  评论(0)    收藏  举报