Skip to main content

        Run the same test with different inputs without duplicating code. Inline tuples or YAML, your call.

pytest Parametrization: Injecting Data Into Tests

Run the same test with different inputs without duplicating code. Inline tuples or YAML, your call.

As already mentioned in a previous article, pytest is a powerful testing framework for Python that offers a wide range of features, including parametrization. Parametrization allows you to run the same test with different sets of input data, making it easier to test multiple scenarios without duplicating code.

Parameterizing tests

pytest provides the @pytest.mark.parametrize decorator to parameterize tests. This decorator takes two arguments: the name of the parameter(s) and a list of input data. Here’s a simple example:

import pytest

@pytest.mark.parametrize("num1, num2, expected", [
    (2, 3, 5),
    (4, 5, 9),
    (6, 7, 13),
])
def test_add(num1, num2, expected):
    assert num1 + num2 == expected

In this example, the test_add function is parameterized with three arguments: num1, num2, and expected. The decorator specifies the input data as a list of tuples, where each tuple represents one set of test inputs.

Parameterizing tests via a test data file

Another option for generating inputs is using a file as a config or manifest. In this example we use YAML.

import pytest
import yaml

# Load test data from YAML file
with open("test_data.yaml", "r") as file:
    test_data = yaml.safe_load(file)


# Parametrized test for making HTTP requests
@pytest.mark.parametrize("payload", test_data["user_payloads"])
def test_post_user(payload):
    import requests
    url = "http://api.example.com/users"
    response = requests.post(url, json=payload)
    assert response.status_code == 201
    assert response.json()["name"] == payload["name"]
    assert response.json()["age"] == payload["age"]


# Parametrized test for database operations
@pytest.mark.parametrize("user", test_data["users"])
def test_insert_user(user):
    import database
    db = database.connect()
    cursor = db.cursor()
    query = "INSERT INTO users (name, age) VALUES (%s, %s)"
    cursor.execute(query, (user["name"], user["age"]))
    db.commit()
    cursor.close()
    db.close()
    # Add assertions to verify the insertion


# Parametrized test for file operations
@pytest.mark.parametrize("file_data", test_data["file_contents"])
def test_write_file(file_data, tmp_path):
    file_path = tmp_path / "test_file.txt"
    with open(file_path, "w") as file:
        file.write(file_data["content"])
    # Add assertions to verify the file content

We load the test data from a YAML file named test_data.yaml. The YAML file could look like this:

user_payloads:
  - name: Alice
    age: 25
  - name: Bob
    age: 30
  - name: Charlie
    age: 35

users:
  - name: David
    age: 40
  - name: Eve
    age: 45

file_contents:
  - content: "This is a test file."
  - content: "Another test file content."

We then define three parametrized tests:

  • test_post_user - parameterized with user_payloads from the YAML file. It makes HTTP POST requests to /users with the specified payloads and verifies the response.
  • test_insert_user - parameterized with users from the YAML file. It connects to a database and inserts the user data.
  • test_write_file - parameterized with file_contents from the YAML file. It creates temporary files and writes the specified content to them.

By using parametrization and reading test data from a YAML file, you can easily manage and maintain test cases for different scenarios. The YAML file provides a centralized location for storing test data, making it easier to modify or add new test cases without modifying the test code itself.