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

工具主要特性:
-
简洁的Swing图形界面,非技术人员也能轻松使用
-
选择Excel文件后,一键生成符合公司模板的Word文档
-
保持原有的所有格式和样式
核心思路与技术选型
为了实现这个工具,我设计了如下的技术架构:

技术栈的考量:
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包生成以及工具使用
-
用idea 生成jar包 Project Structure -> Artifacts -> 添加jar -> From modules with denpendencies -> main class 选自己工程的启动class 其他的不用动 -> ok
-
Build -> Build Artifacts -> Build 然后去out目录下找自己的jar文件
-
准备Excel文件:确保包含必要的列(用例ID、标题、步骤等)
-
运行工具:双击TestCaseConverter.jar,或者本地写一个bat文件,用java -jar test.jar 来启动,需要修改启动参数时
-
选择文件:点击"选择Excel"按钮
-
生成文档:点击"处理文件"
-
完成:在指定位置查看生成的Word文档
本文来自博客园,作者:暴躁牛马,转载请注明原文链接:https://www.cnblogs.com/listen2life/p/19311086

浙公网安备 33010602011771号