1. 简介

本文将介绍用于测试 PDF 文件的 PDFUnit 库。通过 PDFUnit 提供的强大 API,我们可以操作 PDF 文件并验证文本、图像、书签等多种内容。

使用 PDFUnit 可以编写相当复杂的测试用例,但本文将从最常见的应用场景入手,这些场景适用于大多数生产环境中的 PDF 文件,并为后续开发提供坚实基础。

⚠️ 重要提示:PDFUnit 仅免费用于评估目的,商业用途需授权。

2. 安装与配置

当前版本的 PDFUnit(2016.05)未在 Maven 中央仓库提供。因此需要手动下载并安装 JAR 包。请参考官方站点说明完成手动安装。

3. 页数验证

先从一个简单示例开始:验证指定 PDF 文件的页数:

@Test
public void givenSinglePage_whenCheckForOnePage_thenSuccess() {
 
    String filename = getFilePath("sample.pdf");
    AssertThat.document(filename)
      .hasNumberOfPages(1);
}

getFilePath() 是与 PDFUnit 无关的辅助方法,仅返回 PDF 文件路径字符串。

所有 PDFUnit 测试都从 AssertThat.document() 调用开始,该方法准备待测文档。hasNumberOfPages() 接收一个 int 参数,指定 PDF 必须包含的页数。本例中 sample.pdf 仅包含一页,测试通过。

若实际页数与参数不符,将抛出异常。以下示例演示异常场景测试:

@Test(expected = PDFUnitValidationException.class)
public void givenMultiplePages_whenCheckForOnePage_thenException() {
    String filename = getFilePath("multiple_pages.pdf");
    AssertThat.document(filename)
      .hasNumberOfPages(1);
}

由于 multiple_pages.pdf 包含多页,将抛出 PDFUnitValidationException 异常。

4. 密码保护文件

处理密码保护文件同样简单。唯一区别在于调用 AssertThat.document() 时需传入文件密码作为第二个参数

@Test
public void givenPwdProtected_whenOpenWithPwd_thenSuccess() {
    String filename = getFilePath("password_protected.pdf");
    String userPassword = "pass1";

    AssertThat.document(filename, userPassword)
      .hasNumberOfPages(1);
}

5. 文本比较

现在比较测试 PDF(sample.pdf)与参考 PDF(sample_reference.pdf)。若待测文件文本与参考文件一致,测试通过:

@Test
public void whenMatchWithReferenceFile_thenSuccess() {
    String testFileName = getFilePath("sample.pdf");
    String referenceFileName = getFilePath("sample_reference.pdf");

    AssertThat.document(testFileName)
      .and(referenceFileName)
      .haveSameText();
}

haveSameText() 方法负责比较两文件的文本内容。

若无需比较完整文本,只需验证特定页面是否存在某文本,可使用 containing() 方法:

@Test
public void whenPage2HasExpectedText_thenSuccess() {
 
    String filename = getFilePath("multiple_pages.pdf");
    String expectedText = "Chapter 1, content";
 
    AssertThat.document(filename)
      .restrictedTo(PagesToUse.getPage(2))
      .hasText()
      .containing(expectedText);
}

只要 multiple_pages.pdf 的第 2 页任意位置包含 expectedText,测试即通过。其他文本的存在与否不影响结果。

进一步限制验证范围:检查特定页面区域是否包含指定文本。需先理解 PageRegion 概念:

PageRegion 是页面内的矩形子区域,必须完全位于页面内。若超出页面边界将报错。其定义包含四个要素:

  1. leftX – 区域左边界距页面左边缘的毫米数
  2. upperY – 区域上边界距页面顶部的毫米数
  3. width – 区域宽度(毫米)
  4. height – 区域高度(毫米)

通过以下属性创建 PageRegion 示例:

  • leftX = 20
  • upperY = 10
  • width = 150
  • height = 50

可视化表示如下: PageRegion

理解概念后,测试用例就简单了:

@Test
public void whenPageRegionHasExpectedtext_thenSuccess() {
    String filename = getFilePath("sample.pdf");
    int leftX = 20;
    int upperY = 10;
    int width = 150;
    int height = 50;
    PageRegion regionTitle = new PageRegion(leftX, upperY, width, height);

    AssertThat.document(filename)
      .restrictedTo(PagesToUse.getPage(1))
      .restrictedTo(regionTitle)
      .hasText()
      .containing("Adobe Acrobat PDF Files");
}

我们在 PDF 第 1 页创建 PageRegion 并验证该区域文本。

6. 书签验证

以下是两个书签相关测试用例:

@Test
public void whenHasBookmarks_thenSuccess() {
    String filename = getFilePath("with_bookmarks.pdf");

    AssertThat.document(filename)
      .hasNumberOfBookmarks(5);
}

当 PDF 文件恰好包含 5 个书签时测试通过。

也可验证书签标签:

@Test
public void whenHasBookmarksWithLabel_thenSuccess() {
    String filename = getFilePath("with_bookmarks.pdf");

    AssertThat.document(filename)
      .hasBookmark()
      .withLabel("Chapter 2")
      .hasBookmark()
      .withLinkToPage(3);
}

此代码验证 PDF 包含文本为 "Chapter 2" 的书签,并检查是否存在指向第 3 页的书签。

7. 图像验证

图像是 PDF 文档的重要部分。单元测试 PDF 内嵌图像同样简单:

@Test
public void whenHas2DifferentImages_thenSuccess() {
    String filename = getFilePath("with_images.pdf");

    AssertThat.document(filename)
      .hasNumberOfDifferentImages(2);
}

此测试验证 PDF 内恰好使用两张不同图像。不同图像数量指 PDF 文档实际存储的图像数量。

但可能出现这种情况:文档内存储单个 logo 图像,却在每页重复显示。此时可见图像数量可能多于不同图像数量。验证可见图像数量:

@Test
public void whenHas2VisibleImages_thenSuccess() {
    String filename = getFilePath("with_images.pdf");
    AssertThat.document(filename)
      .hasNumberOfVisibleImages(2);
}

PDFUnit 支持逐字节比较图像内容,这意味着 PDF 内图像与参考图像必须完全一致。由于字节级比较,不同格式(如 BMP 和 PNG)被视为不同图像:

@Test
public void whenImageIsOnAnyPage_thenSuccess() {
    String filename = getFilePath("with_images.pdf");
    String imageFile = getFilePath("Superman.png");

    AssertThat.document(filename)
      .restrictedTo(AnyPage.getPreparedInstance())
      .hasImage()
      .matching(imageFile);
}

注意 AnyPage 的使用:我们不限制图像出现在特定页面,而是检查整个文档的任意页面。

比较图像可接受多种形式:BufferedImageFileInputStreamURL 或文件名字符串。

8. 嵌入文件验证

某些 PDF 文档包含嵌入文件或附件,同样需要测试:

@Test
public void whenHasEmbeddedFile_thenSuccess() {
    String filename = getFilePath("with_attachments.pdf");
 
    AssertThat.document(filename)
      .hasEmbeddedFile();
}

此代码验证文档至少包含一个嵌入文件。

也可验证嵌入文件名称:

@Test
public void whenHasmultipleEmbeddedFiles_thenSuccess() {
    String filename = getFilePath("with_attachments.pdf");

    AssertThat.document(filename)
      .hasNumberOfEmbeddedFiles(4)
      .hasEmbeddedFile()
      .withName("complaintform1.xls")
      .hasEmbeddedFile()
      .withName("complaintform2.xls")
      .hasEmbeddedFile()
      .withName("complaintform3.xls");
}

更进一步,可验证嵌入文件内容

@Test
public void whenEmbeddedFileContentMatches_thenSuccess() {
    String filename = getFilePath("with_attachments.pdf");
    String embeddedFileName = getFilePath("complaintform1.xls");

    AssertThat.document(filename)
      .hasEmbeddedFile()
      .withContent(embeddedFileName);
}

本节示例均直观易懂。

9. 总结

本文通过多个示例覆盖了 PDF 测试的常见场景。但 PDFUnit 功能远不止于此,建议访问官方文档探索更多高级用法。


原始标题:Guide to PDFUnit

« 上一篇: Java周报,187