如何为PDFBox生成的合并PDF编写页面顺序验证单元测试?
验证PDF合并页面顺序的单元测试方案
核心思路
要验证合并后的页面顺序,关键是给每个源PDF的页面打上唯一可识别标记,合并后提取这些标记并与预期排序对比。用Apache PDFBox(和你当前使用的PDFMergerUtility同属Apache生态,兼容性好)解析合并后的PDF并提取标记是最优选择。
1. 准备测试用PDF文件
给测试PDF的每页添加独特标识,常用两种方式:
- 文本标识:每页写入固定格式的唯一文本(如第一份PDF每页写
PDF_01_Page_01、PDF_01_Page_02,第二份写PDF_02_Page_01) - 元数据标识:给每页的PDF字典添加自定义键值对(如
SourceFile字段,值为源文件名_页码)
2. 编写页面标识提取工具函数
用PDFBox实现提取逻辑:
import org.apache.pdfbox.pdmodel.PDDocument import org.apache.pdfbox.text.PDFTextStripper // 提取页面文本标识 fun extractPageTextIdentifiers(mergedPdfFile: File): List<String> { return PDDocument.load(mergedPdfFile).use { document -> val textStripper = PDFTextStripper() (1..document.numberOfPages).map { pageNum -> textStripper.startPage = pageNum textStripper.endPage = pageNum textStripper.getText(document).trim().split("\n").firstOrNull() ?: "UNKNOWN_PAGE_$pageNum" } } } // 提取自定义元数据标识(如果用元数据方式) fun extractPageSourceMetadata(mergedPdfFile: File): List<String> { return PDDocument.load(mergedPdfFile).use { document -> document.pages.map { page -> page.cosDictionary.getString("SourceFile") ?: "UNKNOWN_SOURCE" } } }
3. 编写单元测试
用JUnit 5的@TempDir管理临时文件,示例代码如下:
import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File import java.util.Date import kotlin.test.assertEquals import org.apache.pdfbox.pdmodel.PDDocument import org.apache.pdfbox.pdmodel.PDPage import org.apache.pdfbox.pdmodel.PDPageContentStream import org.apache.pdfbox.pdmodel.font.PDType1Font class PdfMergeTest { @TempDir lateinit var tempDir: File @Test fun `merge PDFs maintains order by filename`() { // 1. 按文件名顺序准备测试PDF val testFiles = listOf( createTestPdf("01_First.pdf", listOf("PDF_01_Page_1", "PDF_01_Page_2")), createTestPdf("02_Second.pdf", listOf("PDF_02_Page_1")), createTestPdf("03_Third.pdf", listOf("PDF_03_Page_1", "PDF_03_Page_2", "PDF_03_Page_3")) ) // 2. 生成预期的页面标识列表 val expectedIdentifiers = testFiles.flatMap { file -> val prefix = file.nameWithoutExtension.replace("_", "") (1..countPages(file)).map { "${prefix}_Page_$it" } } // 3. 执行合并操作 val mergedFile = testFiles.mergePdfsToOne( path = tempDir.path, newFileTitle = "merged_test.pdf", parentZipFileModifiedDate = Date(), dp = mockDependencyProvider() ) // 4. 提取合并后的页面标识 val actualIdentifiers = extractPageTextIdentifiers(mergedFile) // 5. 断言顺序完全匹配 assertEquals(expectedIdentifiers, actualIdentifiers) } // 辅助:创建带指定文本的测试PDF private fun createTestPdf(filename: String, pageTexts: List<String>): File { val file = File(tempDir, filename) PDDocument().use { document -> pageTexts.forEach { text -> val page = document.addPage(PDPage()) PDPageContentStream(document, page).use { contentStream -> contentStream.beginText() contentStream.setFont(PDType1Font.HELVETICA, 12f) contentStream.newLineAtOffset(100f, 700f) contentStream.showText(text) contentStream.endText() } } document.save(file) } return file } // 辅助:统计PDF页数 private fun countPages(file: File): Int { return PDDocument.load(file).use { it.numberOfPages } } // 辅助:Mock依赖提供者(根据实际类型调整) private fun mockDependencyProvider(): INT202DependencyProvider { return object : INT202DependencyProvider { override val integrationId = /* 返回测试用的integrationId值 */ } } }
关键注意事项
- 确保测试用PDF的文件名排序逻辑和业务需求一致(如处理数字前缀的自然排序,避免字符串排序导致的
10排在2前面的问题) - 若业务中存在重复文件名的源PDF,需给每个文件添加更独特的标识(如文件哈希值+页码)
- 添加PDFBox测试依赖到build.gradle.kts:
testImplementation("org.apache.pdfbox:pdfbox:2.0.32")
内容的提问来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

