You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 03:15:56