如何对绘制PDF图形的Python函数进行单元测试?

21

我正在编写一个使用Cairo图形库输出PDF文件的CAD应用程序。许多单元测试不需要实际生成PDF文件,例如计算对象的预期边界框。但是,我想确保在更改代码后生成的PDF文件“看起来”正确。有没有自动化的方法可以做到这一点?如何尽可能地自动化?我需要手动检查每个生成的PDF吗?如何解决这个问题而不把头发都揪光?


你可以看一下如何测试 matplotlibsage 的绘图功能。 - jfs
我在这里发布了一个类似的问题:https://dev59.com/OnM_5IYBdhLWcg3wmkUK#21692319 - gzerone
6个回答

24

(参见下面的更新!)

我正在使用 Linux 上的 shell 脚本执行相同的操作,其中包装了以下几个工具:

  1. ImageMagick 的 compare 命令
  2. pdftk 工具
  3. Ghostscript(可选)

(将其移植到 DOS/Windows 的 .bat 批处理文件中将非常容易。)

我的应用程序创建了一些参考 PDF,这些 PDF 是“已知好”的。代码更改后,新生成的 PDF 会与这些参考 PDF 进行像素级比较,并保存为一个新的 PDF。在这个 PDF 中,所有未更改的像素都涂成白色,而所有不同的像素都涂成红色。

以下是构建块:

pdftk

使用此命令将多页 PDF 文件拆分为多个单页 PDF:

pdftk  reference.pdf  burst  output  somewhere/reference_page_%03d.pdf
pdftk  comparison.pdf burst  output  somewhere/comparison_page_%03d.pdf

比较

使用此命令为每个页面创建一个“差异”PDF页面:

compare \
       -verbose \
       -debug coder -log "%u %m:%l %e" \
        somewhere/reference_page_001.pdf \
        somewhere/comparison_page_001.pdf \
       -compose src \
        somewhereelse/reference_diff_page_001.pdf

Ghostscript

由于自动插入的元数据(如当前日期+时间),基于MD5哈希值的文件比较在PDF输出方面表现不佳。

如果您想自动发现所有仅包含纯白页的情况,您还可以使用bmp256输出设备将其转换为无元数据的位图格式。您可以对原始PDF(参考和比较)或diff-PDF页面执行此操作:

 gs \
   -o reference_diff_page_001.bmp \
   -r72 \
   -g595x842 \
   -sDEVICE=bmp256 \
    reference_diff_page_001.pdf

 md5sum reference_diff_page_001.bmp
 

如果对于一张595x842的全白色页面,其MD5sum与您所期望的相符,则您的单元测试已经通过。


更新:

我不知道为什么之前我没有想到使用ImageMagick的compare生成一张直方图输出...

以下是一个由2个不同命令链接起来的命令管道:

  1. 第一个命令与上面提到的compare相同,生成'白色像素相等,红色像素不同'-格式,但它将输出ImageMagick内部的miff格式。它不会写入文件,而是输出到stdout
  2. 第二个命令使用convert读取stdin,生成直方图,并以文本形式输出结果。将会有两行:
    • 一个指示白色像素数量
    • 另一个指示红色像素数量。

这就是它:

compare \
   reference.pdf \
   current.pdf \
  -compose src \
   miff:- \
| \
convert \
   - \
  -define histogram:unique-colors=true \
  -format %c \
   histogram:info:-

输出示例:

 56934: (61937,    0, 7710,52428) #F1F100001E1ECCCC srgba(241,0,30,0.8)
444056: (65535,65535,65535,52428) #FFFFFFFFFFFFCCCC srgba(255,255,255,0.8)

(这个示例输出是通过使用这些参考文献.pdf当前文献.pdf文件生成的。)

我认为这种类型的输出非常适合自动化单元测试。如果你评估这两个数字,你可以很容易地计算出“红像素”的百分比,甚至可以根据某个阈值返回PASSEDFAILED(如果由于某种原因你不一定需要“零红色”)。


1
非常感谢您提供的详细步骤!在我的情况下,我正在使用矢量图形,并且只想知道是否有任何更改,因此最终使用了二进制diff。我创建了一个名为“known_good”的子目录,其中包含“人工验证”的PDF文件,并且此代码执行实际比较:def different(a,b): return subprocess.call(['diff', a, b, '--brief']) != 0。(这使用了Mac OS X diff命令;Python中有一个便携式命令。)如果我需要检查的PDF文件太多或生成器是不确定性的,则此方法将无法正常工作,但到目前为止,看起来我的问题已经解决了。 - Nathan Farrington

8
你可以将PDF捕获为位图(或至少是无损压缩的图像),然后将每个测试生成的图像与其应该看起来像的参考图像进行比较。任何差异都会被标记为测试中的错误。

2
在我的情况下,PDF 图形存储为向量,因此文件大小非常小。我使用 Cairo 进行了测试,结果发现 PDF 生成是确定性的,这意味着一个简单的“diff”就足以标记错误,就像你所建议的那样。 - Nathan Farrington
请注意,这取决于cairo的版本。不同版本的cairo库可能会生成略有不同的PDF输出。 - Uli Schlachter

0
我用Python为我的雇主的文档编写了一个工具来验证PDF文件。它可以将单个页面与主图像进行比较。我使用了一个叫做swftools的库将页面导出为PNG,然后使用Python Imaging Library将其与主图像进行比较。
相关代码看起来像这样(由于脚本的其他部分有一些依赖关系,因此此代码无法运行,但您应该能够理解):
# exporting

gfxpdf = gfx.open("pdf", self.pdfpath)
if os.path.isfile(pngPath):
    os.remove(pngPath)
page = gfxpdf.getPage(pagenum)
img = gfx.ImageList()
img.startpage(page.width, page.height)
page.render(img)
img.endpage()
img.save(pngPath)
return os.path.isfile(pngPath)

# comparing

outPng = os.path.join(outpath, pngname)
masterPng = os.path.join(outpath, "_master", pngname)
if os.path.isfile(masterPng):
    output = Image.open(outPng).convert("RGB") # discard alpha channel, if any
    master = Image.open(masterPng).convert("RGB")
    mismatch = any(x[1] for x in ImageChops.difference(output, master).getextrema())

0
我脑海中浮现的第一个想法是使用差异工具。这些工具通常用于比较文档的文本,但它们也可以比较 PDF 的布局。使用它,您可以将预期输出与提供的输出进行比较。
谷歌给出的第一个结果是this。虽然它是商业软件,但可能有其他免费/开源的替代品。

0

我建议使用xpresser - (https://wiki.ubuntu.com/Xpresser)。您可以尝试将图像与相似的图像匹配,而不是完全相同的副本 - 这是这些情况下的问题。

我不知道xpresser是否正在积极开发中,或者它是否可以与独立的图像文件一起使用(我认为可以) - 无论如何,它从Sikuli项目中汲取了灵感(该项目是Java和Jython前端,而xpresser是Python)。


0

“cmppdf” 可以比较 PDF 文件的视觉外观或文本内容。

这是一个 bash 脚本,可从 https://abhweb.org/jima/cmppdf?v 下载。

它使用 pdftkcompare 来进行图形化比较 PDF 文件,类似于其他答案中描述的内容。元数据(不影响实际外观的任何内容)不会被比较。

文本比较选项使用 pdftotxtdiff


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接