Skip to main content
Insyrge - Zoho CRM & Business Automation Consulting
← Back to All ArticlesAI & Business Automation

Pytest for Automation Scripts: Test Before Runs

How to add pytest tests to Python automation scripts so changes to parsing, mapping and file handling are caught before they run.

•Manav Sharma
Pytest for Automation Scripts: Test Before Runs

Use pytest to test Python automation scripts: split logic from side effects, test parsing and mapping, use tmp_path and monkeypatch, run tests on change.

Python automation scripts tend to start small: read a spreadsheet, clean a few columns, push the rows into a CRM. Over time they gain special cases, new input formats and extra destinations, and every change risks breaking something that used to work. Because these scripts often run unattended, a mistake can quietly corrupt data for days before anyone notices. Pytest gives you a cheap way to check the logic before each change goes live. You write small functions that call your code with known inputs and assert on the results, and pytest runs them all in seconds. This guide shows how to structure scripts so they are testable, what to test first and how to run tests automatically.

Why scripts need tests too

It is tempting to think tests are for large applications and not for a two-hundred-line script. But scripts that move business data carry real risk. A parsing change that misreads a date format, a mapping change that sends the wrong value to a CRM field or a file-handling change that overwrites yesterday's export can all cause damage that takes longer to fix than the tests would have taken to write. Our guide to Python automation scripts covers the habits that keep scripts dependable in production; tests are what let you change them with confidence.

Why pytest suits automation work

The pytest documentation describes the framework as making it easy to write small, readable tests that can scale to support complex functional testing (pytest documentation). For scripts, three features matter most:

  • Plain assert statements: you write normal Python comparisons, and pytest reports clearly what differed when a test fails
  • Fixtures: reusable setup, such as sample input data or a temporary folder, shared across tests
  • Built-in helpers for files and environments, such as tmp_path and monkeypatch

You do not need classes or special syntax. A test is a function whose name starts with test, in a file whose name starts with test.

Separate logic from side effects

The biggest step towards testable scripts is structural. Code that reads files, calls APIs and writes to databases is hard to test directly. Code that takes data in and returns data out is easy to test. Split your script accordingly:

  • Pure functions for parsing, cleaning, mapping and validation
  • Thin functions that read inputs and write outputs
  • A main function that wires them together

Then test the pure functions thoroughly and the input and output functions lightly. This structure also makes the script easier to read and reuse.

What to test first in pytest

Start where mistakes are most likely and most costly:

  • Parsing of dates, numbers and currencies in every format you have seen in real files
  • Mapping rules from source fields to target fields, including defaults and empty values
  • Validation rules that decide whether a record is accepted or rejected
  • Deduplication and matching logic
  • Edge cases you have been bitten by before, such as blank rows, extra whitespace or unexpected encodings

Each time a bug is found in production, add a test that reproduces it before fixing it. Over time, the test suite becomes a record of every format and edge case the script has to handle.

Use tmp_path and monkeypatch

Scripts often read and write files. The pytest documentation explains that the tmp_path fixture provides a temporary directory unique to each test function, as a pathlib.Path object (pytest: temporary directories). Tests can write sample input files there and check output files without touching real folders.

Scripts also read settings from environment variables. The monkeypatch fixture lets a test set or remove environment variables, or replace a function, for the duration of that test only. Use it to test configuration handling and to replace network calls with fake responses.

Worked example: testing a parsing function

The simplified, untested sketch below shows a parsing function and three pytest tests: two for the parser and one that uses tmp_path to check file output. The function names and formats are hypothetical.

# Simplified, untested sketch
from decimal import Decimal
import csv

def parse_amount(text):
    cleaned = text.strip().replace(",", "")
    if not cleaned:
        return None
    return Decimal(cleaned)

def write_rows(path, rows):
    with open(path, "w", newline="") as f:
        writer = csv.writer(f)
        writer.writerows(rows)

def test_parse_amount_handles_thousands_separator():
    assert parse_amount("1,250.50") == Decimal("1250.50")

def test_parse_amount_returns_none_for_blank():
    assert parse_amount("   ") is None

def test_write_rows_creates_file(tmp_path):
    out = tmp_path / "out.csv"
    write_rows(out, [["id", "amount"], ["1", "10.00"]])
    assert out.read_text().splitlines()[1] == "1,10.00"

In a real project the functions would live in the script module and the tests in a separate test file that imports them.

Cover many formats with parametrize

Parsing functions usually need the same check run against many inputs. Rather than writing a separate test for each date or number format, pytest lets you parametrize a test: you list pairs of input and expected output, and pytest runs the test once per pair, reporting each case separately. The pytest documentation has a how-to guide on parametrizing fixtures and test functions. This keeps the test file short while covering every format you have collected from real files, and adding a new case after an incident becomes a one-line change.

Share sample data with fixtures

When several tests need the same sample records, define them once in a fixture and let each test request it by name. Keep fixtures small and realistic, ideally based on anonymised examples of the real input. Never copy real customer data into test files.

Fake the outside world

Tests should not call real CRMs, send real emails or depend on a live API. Replace those calls with fakes:

  • Pass a client object into functions, so tests can pass a fake one
  • Use monkeypatch to replace a network function with one that returns a canned response
  • Save sample API responses as files and load them in tests

Keep one or two separate integration checks that run against a sandbox, if the target system offers one, and run them less often.

Run tests automatically

Tests only help if they run. Add them to your workflow:

  • Run pytest locally before committing
  • Run it in CI on every push or pull request
  • Block deployment of the script if tests fail

For data pipelines, combine code tests with checks on the data itself. Our post on data pipeline testing covers checks on real data, such as row counts and null rates, which complement unit tests of the code.

Keep tests fast and focused

A suite that runs in seconds gets run often. Keep sample data small, avoid sleeping in tests and test one behaviour per function with a descriptive name. When a test fails, its name and the assertion message should tell you what broke without reading the code. Review slow or flaky tests the same way you would review slow code, and fix them rather than skipping them.

Frequently Asked Questions

What is pytest?

It is a Python testing framework. Tests are plain functions that use assert statements, and pytest discovers and runs them, reporting clearly which ones failed and why.

How do I test a script that reads files?

Move parsing and transformation into functions that take data and return results, test those directly, and use the tmp_path fixture to test file reading and writing in a temporary folder.

Should tests call the real CRM or API?

No. Replace network calls with fakes or saved responses in unit tests. Use a sandbox for a small number of separate integration checks.

When should tests run?

Locally before committing and automatically in CI on every change, with deployment blocked when tests fail.

Change scripts without fear

We add tests, structure and monitoring to Python automation so teams can change scripts safely. See our custom app development services, or talk to us about pytest for your scripts.

About the Author

Manav Sharma is the founder of Insyrge and a CRM solutions architect. He works across Zoho, Salesforce, HubSpot, Odoo and more, designing the CRM setups, integrations and automations that keep sales and operations teams working from the same data.

Need Help Implementing This in Your Business?

Our certified Zoho consultants and automation experts can help you design and deploy custom workflows tailored to your operations.

Book Free Consultation