pytest-params


Namepytest-params JSON
Version 0.2.0 PyPI version JSON
download
home_pageNone
SummarySimplified pytest test case parameters.
upload_time2024-08-05 22:49:03
maintainerNone
docs_urlNone
authorJoao Coelho
requires_pythonNone
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pytest-params

[![image](https://img.shields.io/pypi/v/pytest-params.svg)](https://pypi.python.org/pypi/pytest-params)

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](#installation)
2. [Examples](#examples)
   1. [`@params`](#params-1)
      1. [pytest native, no `id`](#pytest-native-no-id)
      2. [pytest native, with `id`](#pytest-native-with-id)
      3. [Using `params`](#using-params)
   2. [`get_request_param`](#get_request_param-1)
3. [Similar projects](#similar-projects)

Under [docs](https://github.com/joaonc/pytest-params/tree/main/docs):
1. [Development](https://github.com/joaonc/pytest-params/blob/main/docs/develop.md)
2. [Publishing to Pypi](https://github.com/joaonc/pytest-params/blob/main/docs/publish.md)

## 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.
```python
@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.
```python
@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)'`.
```python
@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. 

```python
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

* [parametrization](https://github.com/singular-labs/parametrization)
* [pytest-parametrized](https://github.com/coady/pytest-parametrized)

The similarly named project [pytest-param](https://github.com/cr3/pytest-param) (no 's') is around
pytest parametrization, but not about making parameters easier to declare.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pytest-params",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": null,
    "author": "Joao Coelho",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/44/6f/e27acc9a6df409e8b6ec6632a61c86a342bdec59ebad5d79d424532fb47f/pytest_params-0.2.0.tar.gz",
    "platform": null,
    "description": "# pytest-params\n\n[![image](https://img.shields.io/pypi/v/pytest-params.svg)](https://pypi.python.org/pypi/pytest-params)\n\nSimplified pytest test case parameters.\n\n----\n\nThere are two main features in this package: the `@params` decorator and the `get_request_param`\nfunction.\n\nBoth help with passing as much logic as possible to parameters and fixtures, so test cases can be\ndeclarative, ie, the business logic and initializations happen outside the test case and the test\nfocuses on the thing that it's supposed to test.  \n\nHelps with:\n* DRYness.\n* More easily understand the test case.\n* Add/remove test case variants, with different parameters.\n\nNote that this package is not a pytest plugin.\n\n### `@params`\n\nThe main driver for this is that when test cases need parameters and could use a one-liner\ndescription, using `@pytest.mark.parametrize` can get cumbersome.\n\n`@params` is sugar syntax for `@pytest.mark.parametrize` that makes usage easier.\n\n### `get_request_param`\n\nWhen creating pytest fixtures that are to be called indirectly (with `indirect=True`), this small\nfunction facilitates extracting the parameters used in the request, especially when there are\nmultiple parameters.\n\n# TOC\n1. [Installation](#installation)\n2. [Examples](#examples)\n   1. [`@params`](#params-1)\n      1. [pytest native, no `id`](#pytest-native-no-id)\n      2. [pytest native, with `id`](#pytest-native-with-id)\n      3. [Using `params`](#using-params)\n   2. [`get_request_param`](#get_request_param-1)\n3. [Similar projects](#similar-projects)\n\nUnder [docs](https://github.com/joaonc/pytest-params/tree/main/docs):\n1. [Development](https://github.com/joaonc/pytest-params/blob/main/docs/develop.md)\n2. [Publishing to Pypi](https://github.com/joaonc/pytest-params/blob/main/docs/publish.md)\n\n## Installation\n\n```\n$ pip install pytest-params\n```\n\n## Examples\nSome examples are provided here. For more examples with advanced usage, please see the test cases\nunder `tests`.\n\n### `@params`\n\n#### pytest native, no `id`\nThis is the most common and simple usage.  \nNote how in the results report the values are displayed but there's no context provided.  \nAlso (not in this example), sometimes the parameters can't be displayed correctly and what shows\nin the report is confusing.\n```python\n@pytest.mark.parametrize('a, b', [(1, 2), (3, 2), (0, 0)])\ndef test_foo(a, b):\n    ...\n```\n```\n============================= test session starts =============================\ncollecting ... collected 3 items\n\ntest_pytest_params.py::test_foo[1-2] PASSED                              [ 33%]\ntest_pytest_params.py::test_foo[3-2] PASSED                              [ 66%]\ntest_pytest_params.py::test_foo[0-0] PASSED                              [100%]\n\n============================== 3 passed in 0.02s ==============================\n```\n\n#### pytest native, with `id`\nContext provided in each set of parameters. Much nicer in the results report.  \nHowever, not straightforward to see which `id` corresponds to which set of parameters.\n```python\n@pytest.mark.parametrize(\n    'a, b',\n    [(1, 2), (3, 2), (0, 0)],\n    ids=['Normal usage (a>b)', 'Inverted (a<b)', 'Both 0'],\n)\ndef test_foo(a, b):\n    ...\n```\n```\n============================= test session starts =============================\ncollecting ... collected 3 items\n\ntest_pytest_params.py::test_foo[Normal usage (a>b)] PASSED               [ 33%]\ntest_pytest_params.py::test_foo[Inverted (a<b)] PASSED                   [ 66%]\ntest_pytest_params.py::test_foo[Both 0] PASSED                           [100%]\n\n============================== 3 passed in 0.03s ==============================\n```\n\n#### Using `params`\nCompared to the examples above that use pytest's `@pytest.mark.parametrize`:\n* Easier to see which description matches which parameters, given they're all together.\n* Ability to have markers in individual sets of parameters.\n\n  Using pytest's native functionality, this is done by creating instances of `pytest.param` and\n  use that as parameters, which is more convoluted and verbose, making it less readable and overall\n  less used.\n\n  Note that by having these markers, the test cases behave as when `@pytest.mark` is applied to the\n  test function without parameters. For example if you wanted to run only `pri1` tests, using the\n  `-m pri1` parameters would execute only the parameter set `'Inverted (a<b)'`.\n```python\n@params(\n    'a, b',\n    [\n        ('Normal usage (a>b)', 1, 2),\n        ('Inverted (a<b)', 3, 2, pytest.mark.pri1, pytest.mark.nightly),\n        ('Both 0', 0, 0, pytest.mark.skip('BUG-123')),\n    ]\n)\ndef test_foo(a, b):\n    ...\n```\n```\n============================= test session starts =============================\ncollecting ... collected 3 items\n\ntest_pytest_params.py::test_foo[Normal usage (a>b)] PASSED               [ 33%]\ntest_pytest_params.py::test_foo[Inverted (a<b)] PASSED                   [ 66%]\ntest_pytest_params.py::test_foo[Both 0] SKIPPED (BUG-123)                [100%]\nSkipped: BUG-123\n\n================== 2 passed, 1 skipped, 2 warnings in 0.03s ===================\n```\nRunning only the `pri1` tests with `-m pr1` parameter.  \nNote how only the `'Inverted (a<b)'` variant ran, which contains the `pri1` marker.\n```\n============================= test session starts =============================\ncollecting ... collected 3 items / 2 deselected / 1 selected\n\ntest_pytest_params.py::test_foo[Inverted (a<b)] PASSED                   [100%]\n\n================= 1 passed, 2 deselected, 2 warnings in 0.02s =================\n```\n\n### `get_request_param`\n\nThis function helps when a fixture requires multiple arguments.  \nWhen a fixture only requires one parameter, `request.param` can be used.\n\nThe example below shows the `rectangle` fixture using `get_request_param()` and the test cases\nusing that fixture passing the `w` and `h` arguments in the form of a dictionary.\n\nAlso shows different ways to implement test cases, including not using `get_request_param()`.\n\nNote that this example is rather simple and may not illustrate the usefulness of the\n`get_request_param`, but as the test cases grow in number and complexity, this comes in handy. \n\n```python\nfrom dataclasses import dataclass\nimport pytest\nfrom src.pytest_params import params, get_request_param\n\n@dataclass\nclass Rectangle:\n    width: float\n    height: float\n\n    def area(self) -> float:\n        return self.width * self.height\n\n\n@pytest.fixture\ndef rectangle(request):\n    width = get_request_param(request, 'w')\n    height = get_request_param(request, 'h')\n\n    return Rectangle(width=width, height=height)\n\n\n@pytest.mark.parametrize(\n    'w, h, expected',\n    [\n        (3, 2, 6),\n        (2, 4, 8),\n    ],\n)\ndef test_area_1(w, h, expected):\n    \"\"\"\n    Not using the `rectangle` fixture.\n    Need to instantiate `Rectangle` in the test case.\n    \"\"\"\n    rectangle = Rectangle(width=w, height=h)\n    assert rectangle.area() == expected\n\n\n@pytest.mark.parametrize(\n    'rectangle',\n    [\n        {'w': 3, 'h': 2},\n        {'w': 2, 'h': 3},\n    ],\n    indirect=True,\n)\ndef test_area_2(rectangle):\n    \"\"\"\n    Using the `rectangle` fixture with `@pytest.mark.parametrize` and `indirect=True`.\n    Having `indirect=True` means that all arguments are fixtures that will be called indirectly.\n    \"\"\"\n    assert rectangle.area() == 6\n\n\n@params(\n    'rectangle, expected',\n    [\n        ('W > H', {'w': 3, 'h': 2}, 6),\n        ('W < H', {'w': 2, 'h': 4}, 8),\n        ('W = H', {'w': 4, 'h': 4}, 16),\n        ('W is 0', {'w': 0, 'h': 4}, 0),\n        ('Both 0', {'w': 0, 'h': 0}, 0),\n    ],\n    indirect=['rectangle'],\n)\ndef test_area_3(rectangle, expected):\n    \"\"\"\n    Using the `rectangle` fixture with `@params` and `indirect=['rectangle']`.\n    Having `indirect=['rectangle']` means that other parameters can be used without being called\n    indirectly, in this case we can have the `expected` parameter. Note that this is not particular\n    to `@params` and can be done with `@pytest.mark.parametrize` as well.\n    \"\"\"\n    assert rectangle.area() == expected\n```\n```\n============================= test session starts =============================\ncollecting ... collected 9 items\n\ntest_pytest_params.py::test_area_1[3-2-6] PASSED                         [ 11%]\ntest_pytest_params.py::test_area_1[2-4-8] PASSED                         [ 22%]\ntest_pytest_params.py::test_area_2[rectangle0] PASSED                    [ 33%]\ntest_pytest_params.py::test_area_2[rectangle1] PASSED                    [ 44%]\ntest_pytest_params.py::test_area_3[W > H] PASSED                         [ 55%]\ntest_pytest_params.py::test_area_3[W < H] PASSED                         [ 66%]\ntest_pytest_params.py::test_area_3[W = H] PASSED                         [ 77%]\ntest_pytest_params.py::test_area_3[W is 0] PASSED                        [ 88%]\ntest_pytest_params.py::test_area_3[Both 0] PASSED                        [100%]\n\n============================== 9 passed in 0.05s ==============================\n```\n\n### Similar projects\n\n* [parametrization](https://github.com/singular-labs/parametrization)\n* [pytest-parametrized](https://github.com/coady/pytest-parametrized)\n\nThe similarly named project [pytest-param](https://github.com/cr3/pytest-param) (no 's') is around\npytest parametrization, but not about making parameters easier to declare.\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Simplified pytest test case parameters.",
    "version": "0.2.0",
    "project_urls": {
        "Home": "https://github.com/joaonc/pytest-params"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "87a0f02d5e48e16447a30dddcfa463c76d7acecc4520d7f9328e3fd06c4b54d2",
                "md5": "d71f4192db42cd11457daf1c8564c55a",
                "sha256": "605cfeefb6e742438d15adbd16d64e96fc2b0fe4ba18b5626d4da86c210b75dd"
            },
            "downloads": -1,
            "filename": "pytest_params-0.2.0-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "d71f4192db42cd11457daf1c8564c55a",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": null,
            "size": 6822,
            "upload_time": "2024-08-05T22:49:01",
            "upload_time_iso_8601": "2024-08-05T22:49:01.814934Z",
            "url": "https://files.pythonhosted.org/packages/87/a0/f02d5e48e16447a30dddcfa463c76d7acecc4520d7f9328e3fd06c4b54d2/pytest_params-0.2.0-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "446fe27acc9a6df409e8b6ec6632a61c86a342bdec59ebad5d79d424532fb47f",
                "md5": "0f2a004cec80be0eae34050c087ea6b4",
                "sha256": "8f2b3b312594d3ac23d791914a4f401510a7721372de5f28857fd2f91db0cd37"
            },
            "downloads": -1,
            "filename": "pytest_params-0.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "0f2a004cec80be0eae34050c087ea6b4",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 85220,
            "upload_time": "2024-08-05T22:49:03",
            "upload_time_iso_8601": "2024-08-05T22:49:03.400389Z",
            "url": "https://files.pythonhosted.org/packages/44/6f/e27acc9a6df409e8b6ec6632a61c86a342bdec59ebad5d79d424532fb47f/pytest_params-0.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-08-05 22:49:03",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "joaonc",
    "github_project": "pytest-params",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "pytest-params"
}
        
Elapsed time: 1.29078s