Skip to content

Testing⚓︎

PyPDFForm uses pytest for testing and coverage.py for measuring test coverage. To run the tests, execute:

coverage run -m pytest && coverage report --fail-under=100
test

Adding tests⚓︎

Add only the tests needed to cover the changed behavior and maintain 100% coverage. Extend existing test files. If a new test file is absolutely necessary, consult the maintainer before creating it.

Leave expected PDF sample regeneration and visual regression comparison to the maintainer unless they explicitly delegate these tasks.

Generate coverage report⚓︎

To generate a test coverage report, run:

coverage run -m pytest && coverage html

View the coverage report by opening htmlcov/index.html in a browser.

coverage

View the coverage report at http://localhost:8000/htmlcov/index.html.

Test breakdown⚓︎

Although each PyPDFForm test is unique, most follow a general paradigm.

Most library tests can be summarized in three steps:

  • Define the expected PDF file.
  • Use PyPDFForm to generate a PDF from the test inputs.
  • Compare the PDF generated by the test with the expected PDF file.

Consider this example test:

def test_fill(pdf_samples, request):
    expected_path = os.path.join(pdf_samples, "sample_filled.pdf")
    with open(expected_path, "rb+") as f:
        obj = PdfWrapper(os.path.join(pdf_samples, "sample_template.pdf")).fill(
            {
                "test": "test_1",
                "check": True,
                "test_2": "test_2",
                "check_2": False,
                "test_3": "test_3",
                "check_3": True,
            },
        )

        request.config.results["expected_path"] = expected_path
        request.config.results["stream"] = obj.read()

        expected = f.read()

        assert len(obj.read()) == len(expected)
        assert obj.read() == expected

The test starts by defining an expected PDF sample_filled.pdf:

expected_path = os.path.join(pdf_samples, "sample_filled.pdf")

The test then fills sample_template.pdf with a data dictionary using PdfWrapper:

obj = PdfWrapper(os.path.join(pdf_samples, "sample_template.pdf")).fill(
    {
        "test": "test_1",
        "check": True,
        "test_2": "test_2",
        "check_2": False,
        "test_3": "test_3",
        "check_3": True,
    },
)

Include these two lines in most tests to make expected PDF regeneration easier:

request.config.results["expected_path"] = expected_path
request.config.results["stream"] = obj.read()

Finally, the test compares the generated PDF stream with the expected PDF stream:

expected = f.read()

assert len(obj.read()) == len(expected)
assert obj.read() == expected

Most CLI tests can be summarized in four steps:

  • Define the expected PDF file.
  • Write any input data required by the command to tmp_path.
  • Invoke the CLI command to generate a PDF from the test inputs.
  • Compare the PDF generated by the test with the expected PDF file.

Consider this example test:

@pytest.mark.cli_test
def test_fill(pdf_samples, tmp_path):
    expected_path = os.path.join(pdf_samples, "docs", "test_fill_text_check.pdf")
    input_path = os.path.join(tmp_path, "input.yaml")
    output_path = os.path.join(tmp_path, "output.pdf")
    fill_data = {
        "test": "test_1",
        "check": True,
        "test_2": "test_2",
        "check_2": False,
        "test_3": "test_3",
        "check_3": True,
    }

    with open(input_path, "w") as f:
        yaml.safe_dump(fill_data, f)

    result = runner.invoke(
        cli_app,
        [
            "fill",
            os.path.join(pdf_samples, "sample_template.pdf"),
            "-f",
            input_path,
            "-o",
            output_path,
        ],
    )
    assert result.exit_code == 0

    with open(expected_path, "rb") as f1, open(output_path, "rb") as f2:
        expected = f1.read()
        actual = f2.read()

        assert len(expected) == len(actual)
        assert expected == actual

Mark CLI tests with @pytest.mark.cli_test. The runner is a CliRunner from typer.testing, and the command under test is the PyPDFForm CLI app:

import pytest
import yaml
from typer.testing import CliRunner

from PyPDFForm.cli.root import cli_app

runner = CliRunner()

The test starts by defining the expected PDF path, the temporary YAML input path, and the output path:

expected_path = os.path.join(pdf_samples, "docs", "test_fill_text_check.pdf")
input_path = os.path.join(tmp_path, "input.yaml")
output_path = os.path.join(tmp_path, "output.pdf")

The test then writes the fill dictionary to a YAML file in tmp_path:

fill_data = {
    "test": "test_1",
    "check": True,
    "test_2": "test_2",
    "check_2": False,
    "test_3": "test_3",
    "check_3": True,
}

with open(input_path, "w") as f:
    yaml.safe_dump(fill_data, f)

Next, the test invokes the fill command against sample_template.pdf from pdf_samples:

result = runner.invoke(
    cli_app,
    [
        "fill",
        os.path.join(pdf_samples, "sample_template.pdf"),
        "-f",
        input_path,
        "-o",
        output_path,
    ],
)
assert result.exit_code == 0

Finally, the test compares the PDF written by the command with the expected PDF stream:

with open(expected_path, "rb") as f1, open(output_path, "rb") as f2:
    expected = f1.read()
    actual = f2.read()

    assert len(expected) == len(actual)
    assert expected == actual