如何校验两个Excel文件内容一致性?(Apache POI测试场景)
问题描述
我用Apache POI生成Excel文件,想写测试用例验证生成的文件和另一个Excel内容是否一致。一开始试了对比文件校验和,但发现同一流程生成的文件校验和却不一样,内容明明是一致的。
把xlsx文件(本质是ZIP)解压后对比目录内容,发现docProps/core.xml里的创建日期会随测试运行变化,导致文件差异,终端操作如下:
Excel文件file1.xlsx和file2.xlsx由同一流程生成,内容完全一致。
$ unzip file1.xlsx -d /tmp/file1 Archive: file1.xlsx inflating: file1/[Content_Types].xml inflating: file1/_rels/.rels inflating: file1/docProps/app.xml inflating: file1/docProps/core.xml inflating: file1/xl/sharedStrings.xml inflating: file1/xl/styles.xml inflating: file1/xl/workbook.xml inflating: file1/xl/_rels/workbook.xml.rels inflating: file1/xl/worksheets/sheet1.xml $ unzip file2.xlsx -d /tmp/file2 Archive: file2.xlsx inflating: file2/[Content_Types].xml inflating: file2/_rels/.rels inflating: file2/docProps/app.xml inflating: file2/docProps/core.xml inflating: file2/xl/sharedStrings.xml inflating: file2/xl/styles.xml inflating: file2/xl/workbook.xml inflating: file2/xl/_rels/workbook.xml.rels inflating: file2/xl/worksheets/sheet1.xml # File containing Excel creation date $ diff --brief --recursive /tmp/file1 /tmp/file2 Files /tmp/file1/docProps/core.xml and /tmp/file2/docProps/core.xml differ $ diff <(xmllint --format /tmp/file1/docProps/core.xml) <(xmllint --format /tmp/file2/docProps/core.xml) 3c3 < <dcterms:created xsi:type="dcterms:W3CDTF">2024-12-06T12:57:08Z</dcterms:created> --- > <dcterms:created xsi:type="dcterms:W3CDTF">2024-12-06T12:57:28Z</dcterms:created>
我原本考虑解压后忽略创建日期文件再对比,但这种方法不够健壮,而且xls文件不是ZIP格式没法用,想问有没有更可靠的校验方案?
可靠的校验方案
1. 使用Apache POI API直接读取并对比内容
这是最通用的方案,同时支持xls和xlsx格式,直接跳过元数据差异,只校验业务相关内容:
- 分别用POI打开两个Excel文件,获取
Workbook对象 - 对比工作表数量、名称是否一致
- 遍历每个工作表的行和单元格,校验单元格的类型、值(包括公式计算结果)、格式(按需校验)
- 可选校验工作簿样式、共享字符串表等核心配置
示例伪代码:
// 打开两个工作簿 Workbook wb1 = WorkbookFactory.create(new File("file1.xlsx")); Workbook wb2 = WorkbookFactory.create(new File("file2.xlsx")); // 校验工作表数量 if (wb1.getNumberOfSheets() != wb2.getNumberOfSheets()) { return false; } // 遍历每个工作表 for (int i = 0; i < wb1.getNumberOfSheets(); i++) { Sheet sheet1 = wb1.getSheetAt(i); Sheet sheet2 = wb2.getSheetAt(i); // 校验工作表名称 if (!sheet1.getSheetName().equals(sheet2.getSheetName())) { return false; } // 遍历行(处理空行情况) Iterator<Row> rowIter1 = sheet1.iterator(); Iterator<Row> rowIter2 = sheet2.iterator(); while (rowIter1.hasNext() || rowIter2.hasNext()) { Row row1 = rowIter1.hasNext() ? rowIter1.next() : null; Row row2 = rowIter2.hasNext() ? rowIter2.next() : null; if ((row1 == null && row2 != null) || (row1 != null && row2 == null)) { return false; } if (row1 != null && row1.getRowNum() != row2.getRowNum()) { return false; } // 遍历单元格 Iterator<Cell> cellIter1 = row1 != null ? row1.iterator() : Collections.emptyIterator(); Iterator<Cell> cellIter2 = row2 != null ? row2.iterator() : Collections.emptyIterator(); while (cellIter1.hasNext() || cellIter2.hasNext()) { Cell cell1 = cellIter1.hasNext() ? cellIter1.next() : null; Cell cell2 = cellIter2.hasNext() ? cellIter2.next() : null; if ((cell1 == null && cell2 != null) || (cell1 != null && cell2 == null)) { return false; } if (cell1 != null) { // 校验单元格类型 if (cell1.getCellType() != cell2.getCellType()) { return false; } // 根据类型校验值 switch (cell1.getCellType()) { case STRING: if (!cell1.getStringCellValue().equals(cell2.getStringCellValue())) { return false; } break; case NUMERIC: if (Math.abs(cell1.getNumericCellValue() - cell2.getNumericCellValue()) > 1e-6) { return false; } break; case BOOLEAN: if (cell1.getBooleanCellValue() != cell2.getBooleanCellValue()) { return false; } break; case FORMULA: // 可选校验公式本身或计算结果 if (!cell1.getCellFormula().equals(cell2.getCellFormula())) { return false; } // 若需校验计算结果,先触发求值 wb1.getCreationHelper().createFormulaEvaluator().evaluate(cell1); wb2.getCreationHelper().createFormulaEvaluator().evaluate(cell2); if (cell1.getCachedFormulaResultType() != cell2.getCachedFormulaResultType()) { return false; } break; // 按需处理其他单元格类型 } } } } } // 关闭资源 wb1.close(); wb2.close();
2. 标准化Excel文件后对比校验和
如果不想编写大量对比逻辑,可以先消除元数据差异再对比校验和:
- 用POI读取原文件,手动设置固定的创建/修改日期(或清空可变元数据)
- 将标准化后的文件写入临时文件,计算两个临时文件的校验和并对比
示例伪代码:
// 标准化第一个文件 Workbook wb1 = WorkbookFactory.create(new File("file1.xlsx")); // 设置固定创建日期,消除差异 wb1.getCreationHelper().createProperties().setCreated(new Date(0)); // 固定为1970-01-01 File tempFile1 = File.createTempFile("standardized", ".xlsx"); try (FileOutputStream fos = new FileOutputStream(tempFile1)) { wb1.write(fos); } wb1.close(); // 对第二个文件执行同样操作... // 计算校验和并对比 String checksum1 = calculateChecksum(tempFile1); String checksum2 = calculateChecksum(tempFile2); boolean isEqual = checksum1.equals(checksum2); // 清理临时文件 tempFile1.delete(); tempFile2.delete();
3. 针对xlsx的轻量方案(仅适用于OOXML格式)
如果只需要处理xlsx文件,可以解压时排除可变元数据文件再对比:
- 解压时跳过
docProps/core.xml、docProps/app.xml这类包含日期的文件 - 递归对比剩余核心内容(
xl/目录下的工作表、样式等)
终端命令示例:
# 解压时排除可变元数据 unzip file1.xlsx -d /tmp/file1 -x docProps/core.xml docProps/app.xml unzip file2.xlsx -d /tmp/file2 -x docProps/core.xml docProps/app.xml # 对比剩余内容 diff --brief --recursive /tmp/file1 /tmp/file2
注意:此方法不适用于xls格式,优先推荐前两种方案。
内容的提问来源于stack exchange,提问作者ka3ak
相关产品推荐
相关产品推荐

