Skip to main content

Simplified pytest test case parameters.

Project description

pytest-params

image

Simplified pytest test case parameters.


There are two main features in this package: the @params decorator and the get_request_param function.

Both help with passing as much logic as possible to parameters and fixtures, so test cases can be declarative, ie, the business logic and initializations happen outside the test case and the test focuses on the thing that it's supposed to test.

Helps with:

  • DRYness.
  • More easily understand the test case.
  • Add/remove test case variants, with different parameters.

Note that this package is not a pytest plugin.

@params

The main driver for this is that when test cases need parameters and could use a one-liner description, using @pytest.mark.parametrize can get cumbersome.

@params is sugar syntax for @pytest.mark.parametrize that makes usage easier.

get_request_param

When creating pytest fixtures that are to be called indirectly (with indirect=True), this small function facilitates extracting the parameters used in the request, especially when there are multiple parameters.

TOC

  1. Installation
  2. Examples
    1. @params
      1. pytest native, no id
      2. pytest native, with id
      3. Using params
    2. get_request_param
  3. Similar projects

Under docs:

  1. Development
  2. Publishing to Pypi

Installation

$ pip install pytest-params

Examples

Some examples are provided here. For more examples with advanced usage, please see the test cases under tests.

@params

pytest native, no id

This is the most common and simple usage.
Note how in the results report the values are displayed but there's no context provided.
Also (not in this example), sometimes the parameters can't be displayed correctly and what shows in the report is confusing.

@pytest.mark.parametrize('a, b', [(1, 2), (3, 2), (0, 0)])
def test_foo(a, b):
    ...
============================= test session starts =============================
collecting ... collected 3 items

test_pytest_params.py::test_foo[1-2] PASSED                              [ 33%]
test_pytest_params.py::test_foo[3-2] PASSED                              [ 66%]
test_pytest_params.py::test_foo[0-0] PASSED                              [100%]

============================== 3 passed in 0.02s ==============================

pytest native, with id

Context provided in each set of parameters. Much nicer in the results report.
However, not straightforward to see which id corresponds to which set of parameters.

@pytest.mark.parametrize(
    'a, b',
    [(1, 2), (3, 2), (0, 0)],
    ids=['Normal usage (a>b)', 'Inverted (a<b)', 'Both 0'],
)
def test_foo(a, b):
    ...
============================= test session starts =============================
collecting ... collected 3 items

test_pytest_params.py::test_foo[Normal usage (a>b)] PASSED               [ 33%]
test_pytest_params.py::test_foo[Inverted (a<b)] PASSED                   [ 66%]
test_pytest_params.py::test_foo[Both 0] PASSED                           [100%]

============================== 3 passed in 0.03s ==============================

Using params

Compared to the examples above that use pytest's @pytest.mark.parametrize:

  • Easier to see which description matches which parameters, given they're all together.

  • Ability to have markers in individual sets of parameters.

    Using pytest's native functionality, this is done by creating instances of pytest.param and use that as parameters, which is more convoluted and verbose, making it less readable and overall less used.

    Note that by having these markers, the test cases behave as when @pytest.mark is applied to the test function without parameters. For example if you wanted to run only pri1 tests, using the -m pri1 parameters would execute only the parameter set 'Inverted (a<b)'.

@params(
    'a, b',
    [
        ('Normal usage (a>b)', 1, 2),
        ('Inverted (a<b)', 3, 2, pytest.mark.pri1, pytest.mark.nightly),
        ('Both 0', 0, 0, pytest.mark.skip('BUG-123')),
    ]
)
def test_foo(a, b):
    ...
============================= test session starts =============================
collecting ... collected 3 items

test_pytest_params.py::test_foo[Normal usage (a>b)] PASSED               [ 33%]
test_pytest_params.py::test_foo[Inverted (a<b)] PASSED                   [ 66%]
test_pytest_params.py::test_foo[Both 0] SKIPPED (BUG-123)                [100%]
Skipped: BUG-123

================== 2 passed, 1 skipped, 2 warnings in 0.03s ===================

Running only the pri1 tests with -m pr1 parameter.
Note how only the 'Inverted (a<b)' variant ran, which contains the pri1 marker.

============================= test session starts =============================
collecting ... collected 3 items / 2 deselected / 1 selected

test_pytest_params.py::test_foo[Inverted (a<b)] PASSED                   [100%]

================= 1 passed, 2 deselected, 2 warnings in 0.02s =================

get_request_param

This function helps when a fixture requires multiple arguments.
When a fixture only requires one parameter, request.param can be used.

The example below shows the rectangle fixture using get_request_param() and the test cases using that fixture passing the w and h arguments in the form of a dictionary.

Also shows different ways to implement test cases, including not using get_request_param().

Note that this example is rather simple and may not illustrate the usefulness of the get_request_param, but as the test cases grow in number and complexity, this comes in handy.

from dataclasses import dataclass
import pytest
from src.pytest_params import params, get_request_param

@dataclass
class Rectangle:
    width: float
    height: float

    def area(self) -> float:
        return self.width * self.height


@pytest.fixture
def rectangle(request):
    width = get_request_param(request, 'w')
    height = get_request_param(request, 'h')

    return Rectangle(width=width, height=height)


@pytest.mark.parametrize(
    'w, h, expected',
    [
        (3, 2, 6),
        (2, 4, 8),
    ],
)
def test_area_1(w, h, expected):
    """
    Not using the `rectangle` fixture.
    Need to instantiate `Rectangle` in the test case.
    """
    rectangle = Rectangle(width=w, height=h)
    assert rectangle.area() == expected


@pytest.mark.parametrize(
    'rectangle',
    [
        {'w': 3, 'h': 2},
        {'w': 2, 'h': 3},
    ],
    indirect=True,
)
def test_area_2(rectangle):
    """
    Using the `rectangle` fixture with `@pytest.mark.parametrize` and `indirect=True`.
    Having `indirect=True` means that all arguments are fixtures that will be called indirectly.
    """
    assert rectangle.area() == 6


@params(
    'rectangle, expected',
    [
        ('W > H', {'w': 3, 'h': 2}, 6),
        ('W < H', {'w': 2, 'h': 4}, 8),
        ('W = H', {'w': 4, 'h': 4}, 16),
        ('W is 0', {'w': 0, 'h': 4}, 0),
        ('Both 0', {'w': 0, 'h': 0}, 0),
    ],
    indirect=['rectangle'],
)
def test_area_3(rectangle, expected):
    """
    Using the `rectangle` fixture with `@params` and `indirect=['rectangle']`.
    Having `indirect=['rectangle']` means that other parameters can be used without being called
    indirectly, in this case we can have the `expected` parameter. Note that this is not particular
    to `@params` and can be done with `@pytest.mark.parametrize` as well.
    """
    assert rectangle.area() == expected
============================= test session starts =============================
collecting ... collected 9 items

test_pytest_params.py::test_area_1[3-2-6] PASSED                         [ 11%]
test_pytest_params.py::test_area_1[2-4-8] PASSED                         [ 22%]
test_pytest_params.py::test_area_2[rectangle0] PASSED                    [ 33%]
test_pytest_params.py::test_area_2[rectangle1] PASSED                    [ 44%]
test_pytest_params.py::test_area_3[W > H] PASSED                         [ 55%]
test_pytest_params.py::test_area_3[W < H] PASSED                         [ 66%]
test_pytest_params.py::test_area_3[W = H] PASSED                         [ 77%]
test_pytest_params.py::test_area_3[W is 0] PASSED                        [ 88%]
test_pytest_params.py::test_area_3[Both 0] PASSED                        [100%]

============================== 9 passed in 0.05s ==============================

Similar projects

The similarly named project pytest-param (no 's') is around pytest parametrization, but not about making parameters easier to declare.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_params-0.2.0.tar.gz (85.2 kB view hashes)

Uploaded Source

Built Distribution

pytest_params-0.2.0-py2.py3-none-any.whl (6.8 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page