pytest-relaxed


Namepytest-relaxed JSON
Version 2.0.2 PyPI version JSON
download
home_pagehttps://pytest-relaxed.readthedocs.io/
SummaryRelaxed test discovery/organization for pytest
upload_time2024-03-29 15:53:26
maintainerNone
docs_urlNone
authorJeff Forcier
requires_python>=3.6
licenseBSD
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            
|version| |python| |license| |ci| |coverage|

.. |version| image:: https://img.shields.io/pypi/v/pytest-relaxed
    :target: https://pypi.org/project/pytest-relaxed/
    :alt: PyPI - Package Version
.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-relaxed
    :target: https://pypi.org/project/pytest-relaxed/
    :alt: PyPI - Python Version
.. |license| image:: https://img.shields.io/pypi/l/pytest-relaxed
    :target: https://github.com/bitprophet/pytest-relaxed/blob/main/LICENSE
    :alt: PyPI - License
.. |ci| image:: https://img.shields.io/circleci/build/github/bitprophet/pytest-relaxed/main
    :target: https://app.circleci.com/pipelines/github/bitprophet/pytest-relaxed
    :alt: CircleCI
.. |coverage| image:: https://img.shields.io/codecov/c/gh/bitprophet/pytest-relaxed
    :target: https://app.codecov.io/gh/bitprophet/pytest-relaxed
    :alt: Codecov

==============
pytest-relaxed
==============

``pytest-relaxed`` provides 'relaxed' test discovery for pytest.

It is the spiritual successor to https://pypi.python.org/pypi/spec, but is
built for ``pytest`` instead of ``nosetests``, and rethinks some aspects of
the design (such as increased ability to opt-in to various behaviors.)

For a development roadmap, see the maintainer's `roadmap page
<http://bitprophet.org/projects#roadmap>`_.


Rationale
=========

Has it ever felt strange to you that we put our tests in ``tests/``, then name
the files ``test_foo.py``, name the test classes ``TestFoo``, and finally
name the test methods ``test_foo_bar``? Especially when almost all of the code
inside of ``tests/`` is, well, *tests*?

This pytest plugin takes a page from the rest of Python, where you don't have
to explicitly note public module/class members, but only need to hint as to
which ones are private. By default, all files and objects pytest is told to
scan will be considered tests; to mark something as not-a-test, simply prefix
it with an underscore.


Relaxed discovery
=================

The "it's a test by default unless underscored" approach works for files::

    tests
	├── _util.py
	├── one_module.py
	└── another_module.py

It's applied to module members::

    def _helper():
        pass

    def one_thing():
        assert True

    def another_thing():
        assert False

    def yet_another():
        assert _helper() == 'something'

And to class members::

    class SomeObject:
        def behavior_one(self):
            assert True

        def another_behavior(self):
            assert False

        def _helper(self):
            pass

        def it_does_things(self):
            assert self._helper() == 'whatever'

Special cases
-------------

As you might expect, there are a few more special cases around discovery to
avoid fouling up common test extensions:

- Files named ``conftest.py`` aren't treated as tests, because they do special
  pytest things;
- Module and class members named ``setup_(module|class|method|function)`` are
  not considered tests, as they are how pytest implements classic/xunit style
  setup and teardown;
- Objects decorated as fixtures with ``@pytest.fixture`` are, of course,
  also skipped.

Backwards compatibility
-----------------------

If you like the idea of pytest-relaxed but have a large test suite, it may be
daunting to think about "upgrading" it all in one go. It's relatively simple to
arrive at a 'hybrid' test suite where your legacy tests still run normally (as
long as they're already pytest-compatible, which is true for most unittest
suites) but 'relaxed' style tests also work as expected.

- The only change you'll still have to make is renaming 'helper' files (any
  whose name doesn't start with ``test_``) so their names begin with an
  underscore; then, of course, search and replace any imports of such files.
- ``pytest-relaxed`` explicitly sidesteps around anything that looks like
  "classic" test files (i.e. named ``test_*``), allowing pytest's native
  collection to take effect. Such files should not need any alteration.
- Our reporter (display) functionality still works pretty well with legacy
  style tests; test prefixes and suffixes are stripped at display time, so
  ``TestMyThing.test_something`` still shows up as if it was written in relaxed
  style: ``MyThing`` w/ nested ``something``.

    - However, because we don't *collect* such tests, nesting and other
      features we offer won't work until you've renamed the files to not start
      with ``test_``, and changed any classes to not inherit from
      ``unittest.TestCase`` or similar.


Nested class organization
=========================

On top of the relaxed discovery algorithm, ``pytest-relaxed`` also lets you
organize tests in a nested fashion, again like the ``spec`` nose plugin or the
tools that inspired it, such as Ruby's ``rspec``.

This is purely optional, but we find it's a nice middle ground between having a
proliferation of files or suffering a large, flat test namespace making it hard
to see which feature areas have been impacted by a bug (or whatnot).

The feature is enabled by using nested/inner classes, like so::

    class SomeObject:
        def basic_behavior(self):
            assert True

        class init:
            "__init__"

            def no_args_required(self):
                assert True

            def accepts_some_arg(self):
                assert True

            def sets_up_config(self):
                assert False

        class some_method:
            def accepts_whatever_params(self):
                assert False

            def base_behavior(self):
                assert True

            class when_config_says_foo:
                def it_behaves_like_this(self):
                    assert False

            class when_config_says_bar:
                def it_behaves_like_this(self):
                    assert True

Test discovery on these inner classes is recursive, so you *can* nest them as
deeply as you like. Naturally, as with all Python code, sometimes you can have
too much of a good thing...but that's up to you.


Nested class attributes
-----------------------

If you're namespacing your tests via nested classes, you may find yourself
wanting to reference the enclosing "scope" of the outer classes they live in,
such as class attributes. pytest-relaxed automatically copies such attributes
onto inner classes during the test collection phase, allowing you to write code
like this::

    class Outer:
        behavior_one = True

        def outer_test(self):
            assert self.behavior_one

        class Inner:
            behavior_two = True

            def inner_test(self):
                assert self.behavior_one and self.behavior_two

Notably:

- The behavior is nested, infinitely, as you might expect;
- Attributes that look like test classes or methods themselves, are not copied
  (though others, i.e. ones named with a leading underscore, are);
- Only attributes _not_ already present on the inner class are copied; thus
  inner classes may naturally "override" attributes, just as with class
  inheritance.


Other test helpers
==================

``pytest-relaxed`` offers a few other random lightweight test-related utilities
that don't merit their own PyPI entries (most ported from ``spec``), such as:

- ``trap``, a decorator for use on test functions and/or test
  helpers/subroutines which is similar to pytest's own ``capsys``/``capfd``
  fixtures in that it allows capture of stdout/err.

    - It offers a slightly simpler API: it replaces ``sys.(stdout|stderr)`` with
      ``IO`` objects which can be ``getvalue()``'d as needed.
    - More importantly, it can wrap arbitrary callables, which is useful for
      code-sharing use cases that don't easily fit into the design of fixtures.

- ``raises``, a wrapper around ``pytest.raises`` which works as a decorator,
  similar to the Nose testing tool of the same name.


Nested output display
=====================

Continuing in the "port of ``spec`` / inspired by RSpec and friends" vein,
``pytest-relaxed`` greatly enhances pytest's verbose display mode:

- Tests are shown in a nested, tree-like fashion, with 'header' lines shown for
  modules, classes (including nested classes) and so forth.
- The per-test-result lines thus consist of just the test names, and are
  colorized (similar to the built-in verbose mode) based on
  success/failure/skip.
- Headers and test names are massaged to look more human-readable, such as
  replacing underscores with spaces.

*Unlike* ``spec``, this functionality doesn't affect normal/non-verbose output
at all, and can be disabled entirely, allowing you to use the relaxed test
discovery alongside normal pytest verbose display or your favorite pytest
output plugins (such as ``pytest-sugar``.)


Installation & use
==================

As with most pytest plugins, it's quite simple:

- ``pip install pytest-relaxed``;
- Tell pytest where your tests live via the ``testpaths`` option; otherwise
  pytest-relaxed will cause pytest to load all of your non-test code as tests!
- Not required, but **strongly recommended**: configure pytest's default
  filename pattern (``python_files``) to be an unqualified glob (``*``).

    - This doesn't impact (our) test discovery, but pytest's assertion
      'rewriting' (the feature that turns ``assert var == othervar`` into
      ``assert 17 == 2`` during error display) reuses this setting when
      determining which files to manipulate.

- Thus, a recommended ``setup.cfg`` (or ``pytest.ini``, sans the header) is::

    [tool:pytest]
    testpaths = tests
    python_files = *

- Write some tests, as exampled above;
- ``pytest`` to run the tests, and you're done!



            

Raw data

            {
    "_id": null,
    "home_page": "https://pytest-relaxed.readthedocs.io/",
    "name": "pytest-relaxed",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": null,
    "keywords": null,
    "author": "Jeff Forcier",
    "author_email": "jeff@bitprophet.org",
    "download_url": "https://files.pythonhosted.org/packages/b8/e4/4153d3a7bfe5d5cfea0320f41ed63c00e193a91dd2feacc784189720454b/pytest-relaxed-2.0.2.tar.gz",
    "platform": null,
    "description": "\n|version| |python| |license| |ci| |coverage|\n\n.. |version| image:: https://img.shields.io/pypi/v/pytest-relaxed\n    :target: https://pypi.org/project/pytest-relaxed/\n    :alt: PyPI - Package Version\n.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-relaxed\n    :target: https://pypi.org/project/pytest-relaxed/\n    :alt: PyPI - Python Version\n.. |license| image:: https://img.shields.io/pypi/l/pytest-relaxed\n    :target: https://github.com/bitprophet/pytest-relaxed/blob/main/LICENSE\n    :alt: PyPI - License\n.. |ci| image:: https://img.shields.io/circleci/build/github/bitprophet/pytest-relaxed/main\n    :target: https://app.circleci.com/pipelines/github/bitprophet/pytest-relaxed\n    :alt: CircleCI\n.. |coverage| image:: https://img.shields.io/codecov/c/gh/bitprophet/pytest-relaxed\n    :target: https://app.codecov.io/gh/bitprophet/pytest-relaxed\n    :alt: Codecov\n\n==============\npytest-relaxed\n==============\n\n``pytest-relaxed`` provides 'relaxed' test discovery for pytest.\n\nIt is the spiritual successor to https://pypi.python.org/pypi/spec, but is\nbuilt for ``pytest`` instead of ``nosetests``, and rethinks some aspects of\nthe design (such as increased ability to opt-in to various behaviors.)\n\nFor a development roadmap, see the maintainer's `roadmap page\n<http://bitprophet.org/projects#roadmap>`_.\n\n\nRationale\n=========\n\nHas it ever felt strange to you that we put our tests in ``tests/``, then name\nthe files ``test_foo.py``, name the test classes ``TestFoo``, and finally\nname the test methods ``test_foo_bar``? Especially when almost all of the code\ninside of ``tests/`` is, well, *tests*?\n\nThis pytest plugin takes a page from the rest of Python, where you don't have\nto explicitly note public module/class members, but only need to hint as to\nwhich ones are private. By default, all files and objects pytest is told to\nscan will be considered tests; to mark something as not-a-test, simply prefix\nit with an underscore.\n\n\nRelaxed discovery\n=================\n\nThe \"it's a test by default unless underscored\" approach works for files::\n\n    tests\n\t\u251c\u2500\u2500 _util.py\n\t\u251c\u2500\u2500 one_module.py\n\t\u2514\u2500\u2500 another_module.py\n\nIt's applied to module members::\n\n    def _helper():\n        pass\n\n    def one_thing():\n        assert True\n\n    def another_thing():\n        assert False\n\n    def yet_another():\n        assert _helper() == 'something'\n\nAnd to class members::\n\n    class SomeObject:\n        def behavior_one(self):\n            assert True\n\n        def another_behavior(self):\n            assert False\n\n        def _helper(self):\n            pass\n\n        def it_does_things(self):\n            assert self._helper() == 'whatever'\n\nSpecial cases\n-------------\n\nAs you might expect, there are a few more special cases around discovery to\navoid fouling up common test extensions:\n\n- Files named ``conftest.py`` aren't treated as tests, because they do special\n  pytest things;\n- Module and class members named ``setup_(module|class|method|function)`` are\n  not considered tests, as they are how pytest implements classic/xunit style\n  setup and teardown;\n- Objects decorated as fixtures with ``@pytest.fixture`` are, of course,\n  also skipped.\n\nBackwards compatibility\n-----------------------\n\nIf you like the idea of pytest-relaxed but have a large test suite, it may be\ndaunting to think about \"upgrading\" it all in one go. It's relatively simple to\narrive at a 'hybrid' test suite where your legacy tests still run normally (as\nlong as they're already pytest-compatible, which is true for most unittest\nsuites) but 'relaxed' style tests also work as expected.\n\n- The only change you'll still have to make is renaming 'helper' files (any\n  whose name doesn't start with ``test_``) so their names begin with an\n  underscore; then, of course, search and replace any imports of such files.\n- ``pytest-relaxed`` explicitly sidesteps around anything that looks like\n  \"classic\" test files (i.e. named ``test_*``), allowing pytest's native\n  collection to take effect. Such files should not need any alteration.\n- Our reporter (display) functionality still works pretty well with legacy\n  style tests; test prefixes and suffixes are stripped at display time, so\n  ``TestMyThing.test_something`` still shows up as if it was written in relaxed\n  style: ``MyThing`` w/ nested ``something``.\n\n    - However, because we don't *collect* such tests, nesting and other\n      features we offer won't work until you've renamed the files to not start\n      with ``test_``, and changed any classes to not inherit from\n      ``unittest.TestCase`` or similar.\n\n\nNested class organization\n=========================\n\nOn top of the relaxed discovery algorithm, ``pytest-relaxed`` also lets you\norganize tests in a nested fashion, again like the ``spec`` nose plugin or the\ntools that inspired it, such as Ruby's ``rspec``.\n\nThis is purely optional, but we find it's a nice middle ground between having a\nproliferation of files or suffering a large, flat test namespace making it hard\nto see which feature areas have been impacted by a bug (or whatnot).\n\nThe feature is enabled by using nested/inner classes, like so::\n\n    class SomeObject:\n        def basic_behavior(self):\n            assert True\n\n        class init:\n            \"__init__\"\n\n            def no_args_required(self):\n                assert True\n\n            def accepts_some_arg(self):\n                assert True\n\n            def sets_up_config(self):\n                assert False\n\n        class some_method:\n            def accepts_whatever_params(self):\n                assert False\n\n            def base_behavior(self):\n                assert True\n\n            class when_config_says_foo:\n                def it_behaves_like_this(self):\n                    assert False\n\n            class when_config_says_bar:\n                def it_behaves_like_this(self):\n                    assert True\n\nTest discovery on these inner classes is recursive, so you *can* nest them as\ndeeply as you like. Naturally, as with all Python code, sometimes you can have\ntoo much of a good thing...but that's up to you.\n\n\nNested class attributes\n-----------------------\n\nIf you're namespacing your tests via nested classes, you may find yourself\nwanting to reference the enclosing \"scope\" of the outer classes they live in,\nsuch as class attributes. pytest-relaxed automatically copies such attributes\nonto inner classes during the test collection phase, allowing you to write code\nlike this::\n\n    class Outer:\n        behavior_one = True\n\n        def outer_test(self):\n            assert self.behavior_one\n\n        class Inner:\n            behavior_two = True\n\n            def inner_test(self):\n                assert self.behavior_one and self.behavior_two\n\nNotably:\n\n- The behavior is nested, infinitely, as you might expect;\n- Attributes that look like test classes or methods themselves, are not copied\n  (though others, i.e. ones named with a leading underscore, are);\n- Only attributes _not_ already present on the inner class are copied; thus\n  inner classes may naturally \"override\" attributes, just as with class\n  inheritance.\n\n\nOther test helpers\n==================\n\n``pytest-relaxed`` offers a few other random lightweight test-related utilities\nthat don't merit their own PyPI entries (most ported from ``spec``), such as:\n\n- ``trap``, a decorator for use on test functions and/or test\n  helpers/subroutines which is similar to pytest's own ``capsys``/``capfd``\n  fixtures in that it allows capture of stdout/err.\n\n    - It offers a slightly simpler API: it replaces ``sys.(stdout|stderr)`` with\n      ``IO`` objects which can be ``getvalue()``'d as needed.\n    - More importantly, it can wrap arbitrary callables, which is useful for\n      code-sharing use cases that don't easily fit into the design of fixtures.\n\n- ``raises``, a wrapper around ``pytest.raises`` which works as a decorator,\n  similar to the Nose testing tool of the same name.\n\n\nNested output display\n=====================\n\nContinuing in the \"port of ``spec`` / inspired by RSpec and friends\" vein,\n``pytest-relaxed`` greatly enhances pytest's verbose display mode:\n\n- Tests are shown in a nested, tree-like fashion, with 'header' lines shown for\n  modules, classes (including nested classes) and so forth.\n- The per-test-result lines thus consist of just the test names, and are\n  colorized (similar to the built-in verbose mode) based on\n  success/failure/skip.\n- Headers and test names are massaged to look more human-readable, such as\n  replacing underscores with spaces.\n\n*Unlike* ``spec``, this functionality doesn't affect normal/non-verbose output\nat all, and can be disabled entirely, allowing you to use the relaxed test\ndiscovery alongside normal pytest verbose display or your favorite pytest\noutput plugins (such as ``pytest-sugar``.)\n\n\nInstallation & use\n==================\n\nAs with most pytest plugins, it's quite simple:\n\n- ``pip install pytest-relaxed``;\n- Tell pytest where your tests live via the ``testpaths`` option; otherwise\n  pytest-relaxed will cause pytest to load all of your non-test code as tests!\n- Not required, but **strongly recommended**: configure pytest's default\n  filename pattern (``python_files``) to be an unqualified glob (``*``).\n\n    - This doesn't impact (our) test discovery, but pytest's assertion\n      'rewriting' (the feature that turns ``assert var == othervar`` into\n      ``assert 17 == 2`` during error display) reuses this setting when\n      determining which files to manipulate.\n\n- Thus, a recommended ``setup.cfg`` (or ``pytest.ini``, sans the header) is::\n\n    [tool:pytest]\n    testpaths = tests\n    python_files = *\n\n- Write some tests, as exampled above;\n- ``pytest`` to run the tests, and you're done!\n\n\n",
    "bugtrack_url": null,
    "license": "BSD",
    "summary": "Relaxed test discovery/organization for pytest",
    "version": "2.0.2",
    "project_urls": {
        "CI": "https://app.circleci.com/pipelines/github/bitprophet/pytest-relaxed",
        "Changelog": "https://github.com/bitprophet/pytest-relaxed/blob/main/docs/changelog.rst",
        "Homepage": "https://pytest-relaxed.readthedocs.io/",
        "Source": "https://github.com/bitprophet/pytest-relaxed"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "aa3d1f6c7bd9b7b47de5c2ef83462255c0b8f5803f49876f73bd94b8ec53624d",
                "md5": "4acbc2f40680c47dd54c6ee3c8c1bdc5",
                "sha256": "b763256255586b3ec9b3397dc6632a242b4838749a5133638db9b47267edfae2"
            },
            "downloads": -1,
            "filename": "pytest_relaxed-2.0.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4acbc2f40680c47dd54c6ee3c8c1bdc5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 15786,
            "upload_time": "2024-03-29T15:53:25",
            "upload_time_iso_8601": "2024-03-29T15:53:25.028607Z",
            "url": "https://files.pythonhosted.org/packages/aa/3d/1f6c7bd9b7b47de5c2ef83462255c0b8f5803f49876f73bd94b8ec53624d/pytest_relaxed-2.0.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b8e44153d3a7bfe5d5cfea0320f41ed63c00e193a91dd2feacc784189720454b",
                "md5": "86e717d539817c911bbc86981bca94e4",
                "sha256": "956ea028ec30dbbfb680dd8e7b4a7fb8f80a239595e88bace018bf2c0d718248"
            },
            "downloads": -1,
            "filename": "pytest-relaxed-2.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "86e717d539817c911bbc86981bca94e4",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 27876,
            "upload_time": "2024-03-29T15:53:26",
            "upload_time_iso_8601": "2024-03-29T15:53:26.896282Z",
            "url": "https://files.pythonhosted.org/packages/b8/e4/4153d3a7bfe5d5cfea0320f41ed63c00e193a91dd2feacc784189720454b/pytest-relaxed-2.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-29 15:53:26",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "bitprophet",
    "github_project": "pytest-relaxed",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "circle": true,
    "lcname": "pytest-relaxed"
}
        
Elapsed time: 0.24344s