Несмотря на то, что этот вопрос довольно старый, я думаю, что могу внести свой вклад в тему.
У нас есть несколько приложений, генерирующих тонны PDF-файлов. Одно из этих приложений написано на Python, и недавно я хотел написать интеграционные тесты, чтобы проверить, правильно ли работает генерация PDF.
Тестирование генерации PDF - HARD , потому что спецификации для файлов PDF очень сложны и недетерминированы. Два файла PDF, сгенерированные с одинаковыми точными входными данными, будут генерировать разные файлы, поэтому прямое сравнение файлов исключается.
Решение: нам нужно проверить, как они выглядят (потому что ТО должно быть детерминированным!).
В нашем случае PDF-файлы создаются с помощью пакета reportlab
, но это не имеет значения с точки зрения теста, нам просто нужно имя файла или BLOB-объект (байты) из генератора. Нам также нужен файл ожидания, содержащий «хороший» PDF-файл для сравнения с файлом, полученным из генератора.
PDF-файлы преобразуются в изображения и затем сравниваются. Это можно сделать несколькими способами, но мы решили использовать ImageMagick
, потому что он чрезвычайно универсален и очень зрел, с привязками практически для любого языка программирования. Для Python 3 привязки предлагаются пакетом Wand
.
Тест выглядит примерно так: Конкретные детали нашей реализации были удалены, а пример был упрощен:
import os
from unittest import TestCase
from wand.image import Image
from app.generators.pdf import PdfGenerator
DIR = os.path.dirname(__file__)
class PdfGeneratorTest(TestCase):
def test_generated_pdf_should_match_expectation(self):
# `pdf` is the blob of the generated PDF
# If using reportlab, this is what you get calling `getpdfdata()`
# on a Canvas instance, after all the drawing is complete
pdf = PdfGenerator().generate()
# PDFs are vectorial, so we need to set a resolution when
# converting to an image
actual_img = Image(blob=pdf, resolution=150)
filename = os.path.join(DIR, 'expected.pdf')
# Make sure to use the same resolution as above
with Image(filename=filename, resolution=150) as expected:
diff = actual.compare(expected, metric='root_mean_square')
self.assertLess(diff[1], 0.01)
0.01
настолько низок, насколько мы можем терпеть небольшие различия. Учитывая, что diff[1]
изменяется от 0 до 1 с использованием метрики root_mean_square
, мы принимаем здесь разницу до 1% по всем каналам по сравнению с образцом ожидаемого файла.