KansaiRobot
KansaiRobot

Reputation: 9912

Using parameters for pytest when parameters depend on each other

I am writing a pytest test for a library similar to this

from mylibrary use do_some_calculation
 
def test_df_against_angle():
    df=load_some_df()
    angle=30
    result=do_some_calculation(df,angle)
    assertTrue(result)

Now as you can see that test only works for a particular dataframe and for an angle(30)

I have to do this tests for several dataframes and several angles To complicate matters,the angles I should use are different for each dataset

So I have to test that

So I am guessing that I have to use pytest's parameters for that. I know how to put simple values as parameters, (So for example I know how to put parameters so as to use those three csv files and even how to put these in a json file and read it to enter the test) but I am at lost as how to put several types of parameters and that these parameters depend on the other

Ideally also I would like to put this in the conftest.py

Can someone give me some pointers on how to do this?

Upvotes: 0

Views: 82

Answers (2)

Hai Vu
Hai Vu

Reputation: 40723

We can write a function to generate all the test cases:

import pytest


def load_df(filename):
    # TODO: Implement this
    return f"df_{filename}"


def do_some_calculation(df, angle):
    # TODO: Implement this
    return True


def generate_test_data():
    test_combinations = [
        ("data_set1.csv", [0, 30, 60]),
        ("data_set2.csv", [90, 120, 150]),
        ("data_set3.csv", [180, 210, 240]),
    ]

    for filename, angles in test_combinations:
        df = load_df(filename)
        for angle in angles:
            yield df, angle


@pytest.mark.parametrize("df,angle", generate_test_data())
def test_df_against_angle(df, angle):
    result = do_some_calculation(df, angle)
    assert result

Output

test_combinations.py::test_df_against_angle[df_data_set1.csv-0] PASSED
test_combinations.py::test_df_against_angle[df_data_set1.csv-30] PASSED
test_combinations.py::test_df_against_angle[df_data_set1.csv-60] PASSED
test_combinations.py::test_df_against_angle[df_data_set2.csv-90] PASSED
test_combinations.py::test_df_against_angle[df_data_set2.csv-120] PASSED
test_combinations.py::test_df_against_angle[df_data_set2.csv-150] PASSED
test_combinations.py::test_df_against_angle[df_data_set3.csv-180] PASSED
test_combinations.py::test_df_against_angle[df_data_set3.csv-210] PASSED
test_combinations.py::test_df_against_angle[df_data_set3.csv-240] PASSED

Notes

  • The generate_test_data function only load the df once and it is shared among the angles. Take care not to alter the df between tests.
  • We load the df within the generate_test_data function, freeing the test from doing the setup part.

Update

If you want to control the names of the tests, use pytest.param. Replace yield df, angle with

yield pytest.param(df, angle, id=f"df={filename}, angle={angle:>3}")

and you get the following output (note the names of the tests):

test_combinations.py::test_df_against_angle[df=data_set1.csv, angle=  0] PASSED
test_combinations.py::test_df_against_angle[df=data_set1.csv, angle= 30] PASSED
test_combinations.py::test_df_against_angle[df=data_set1.csv, angle= 60] PASSED
test_combinations.py::test_df_against_angle[df=data_set2.csv, angle= 90] PASSED
test_combinations.py::test_df_against_angle[df=data_set2.csv, angle=120] PASSED
test_combinations.py::test_df_against_angle[df=data_set2.csv, angle=150] PASSED
test_combinations.py::test_df_against_angle[df=data_set3.csv, angle=180] PASSED
test_combinations.py::test_df_against_angle[df=data_set3.csv, angle=210] PASSED
test_combinations.py::test_df_against_angle[df=data_set3.csv, angle=240] PASSED

Upvotes: 0

larsks
larsks

Reputation: 311586

You can use the pytest.mark.parametrize decorator to parametrize multiple parameters. For example, if your test function takes as input a filename (the path to a CSV file) and a list of angles, you could write something like:

@pytest.mark.parametrize("filename,angles", (
  ("data_set1.csv", (0, 30, 60)),
  ("data_set2.csv", (90, 120, 150)),
  ("data_set3.csv", (180, 210, 240)),
))
def test_df_against_angle(filename, angles):
  df = load_some_df(filename)
  ...

Given the new information you've left in your comments, you could write your test like this to get the desired nine parametrized tests:

import pytest

from itertools import product


@pytest.mark.parametrize(
    "filename,angle",
    (
        *product(("data_set1.csv",), (0, 30, 60)),
        *product(("data_set2.csv",), (90, 120, 150)),
        *product(("data_set3.csv",), (180, 210, 240)),
    ),
)
def test_angles(filename, angle):
    assert True

This will run the following tests:

$ pytest -v
========================================== test session starts ==========================================
platform linux -- Python 3.11.1, pytest-7.2.0, pluggy-1.0.0 -- /home/lars/.local/share/virtualenvs/python-LD_ZK5QN/bin/python
cachedir: .pytest_cache
rootdir: /home/lars/tmp/python
collected 9 items

test_angles.py::test_angles[data_set1.csv-0] PASSED                                               [ 11%]
test_angles.py::test_angles[data_set1.csv-30] PASSED                                              [ 22%]
test_angles.py::test_angles[data_set1.csv-60] PASSED                                              [ 33%]
test_angles.py::test_angles[data_set2.csv-90] PASSED                                              [ 44%]
test_angles.py::test_angles[data_set2.csv-120] PASSED                                             [ 55%]
test_angles.py::test_angles[data_set2.csv-150] PASSED                                             [ 66%]
test_angles.py::test_angles[data_set3.csv-180] PASSED                                             [ 77%]
test_angles.py::test_angles[data_set3.csv-210] PASSED                                             [ 88%]
test_angles.py::test_angles[data_set3.csv-240] PASSED                                             [100%]

=========================================== 9 passed in 0.01s ===========================================

Upvotes: 1

Related Questions