Merged in parametrized-fixture-override (pull request #257)

allow to override parametrized fixtures with non-parametrized ones and vice versa
This commit is contained in:
Bruno Oliveira 2015-03-12 09:40:56 -03:00
commit eead0365b5
10 changed files with 413 additions and 82 deletions

View File

@ -55,6 +55,7 @@
- "python_classes" and "python_functions" options now support glob-patterns
for test discovery, as discussed in issue600. Thanks Ldiary Translations.
- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
2.6.4
----------

View File

@ -128,22 +128,18 @@ Preparing Pull Requests on Bitbucket
The primary development platform for pytest is BitBucket. You can find all
the issues there and submit your pull requests.
1. Fork the
#. Fork the
`pytest BitBucket repository <https://bitbucket.org/pytest-dev/pytest>`__. It's
fine to use ``pytest`` as your fork repository name because it will live
under your user.
.. _virtualenvactivate:
#. Create a development environment
(will implicitly use http://www.virtualenv.org/en/latest/)::
2. Create and activate a fork-specific virtualenv
(http://www.virtualenv.org/en/latest/)::
$ make develop
$ source .env/bin/activate
$ virtualenv pytest-venv
$ source pytest-venv/bin/activate
.. _checkout:
3. Clone your fork locally using `Mercurial <http://mercurial.selenic.com/>`_
#. Clone your fork locally using `Mercurial <http://mercurial.selenic.com/>`_
(``hg``) and create a branch::
$ hg clone ssh://hg@bitbucket.org/YOUR_BITBUCKET_USERNAME/pytest
@ -153,19 +149,20 @@ the issues there and submit your pull requests.
If you need some help with Mercurial, follow this quick start
guide: http://mercurial.selenic.com/wiki/QuickStart
.. _testing-pytest:
#. Create a development environment
(will implicitly use http://www.virtualenv.org/en/latest/)::
4. You can now edit your local working copy. To test you need to
install the "tox" tool into your virtualenv::
$ make develop
$ source .env/bin/activate
$ pip install tox
#. You can now edit your local working copy.
You need to have Python 2.7 and 3.3 available in your system. Now
You need to have Python 2.7 and 3.4 available in your system. Now
running tests is as simple as issuing this command::
$ python runtox.py -e py27,py33,flakes
$ python runtox.py -e py27,py34,flakes
This command will run tests via the "tox" tool against Python 2.7 and 3.3
This command will run tests via the "tox" tool against Python 2.7 and 3.4
and also perform "flakes" coding-style checks. ``runtox.py`` is
a thin wrapper around ``tox`` which installs from a development package
index where newer (not yet released to pypi) versions of dependencies
@ -176,16 +173,16 @@ the issues there and submit your pull requests.
$ python runtox.py -e py27 -- --pdb
or to only run tests in a particular test module on py33::
or to only run tests in a particular test module on py34::
$ python runtox.py -e py33 -- testing/test_config.py
$ python runtox.py -e py34 -- testing/test_config.py
5. Commit and push once your tests pass and you are happy with your change(s)::
#. Commit and push once your tests pass and you are happy with your change(s)::
$ hg commit -m"<commit message>"
$ hg push -b .
6. Finally, submit a pull request through the BitBucket website:
#. Finally, submit a pull request through the BitBucket website:
.. image:: img/pullrequest.png
:width: 700px
@ -214,5 +211,3 @@ original repository. If you insist on using git with bitbucket/hg you
may try `gitifyhg <https://github.com/buchuki/gitifyhg>`_ but are on your
own and need to submit pull requests through the respective platform,
nevertheless.

25
Makefile Normal file
View File

@ -0,0 +1,25 @@
# Set of targets useful for development/release process
PYTHON = python2.7
PATH := $(PWD)/.env/bin:$(PATH)
# prepare virtual python environment
.env:
virtualenv .env -p $(PYTHON)
# install all needed for development
develop: .env
pip install -e . tox -r requirements-docs.txt
# clean the development envrironment
clean:
-rm -rf .env
# generate documentation
docs: develop
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc
cd doc/en; make html
# upload documentation
upload-docs: develop
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc --update
cd doc/en; make install

View File

@ -1712,13 +1712,17 @@ class FixtureManager:
def pytest_generate_tests(self, metafunc):
for argname in metafunc.fixturenames:
faclist = metafunc._arg2fixturedefs.get(argname)
if faclist is None:
continue # will raise FixtureLookupError at setup time
for fixturedef in faclist:
if faclist:
fixturedef = faclist[-1]
if fixturedef.params is not None:
func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
# skip directly parametrized arguments
if argname not in func_params and argname not in func_params[0]:
metafunc.parametrize(argname, fixturedef.params,
indirect=True, scope=fixturedef.scope,
ids=fixturedef.ids)
else:
continue # will raise FixtureLookupError at setup time
def pytest_collection_modifyitems(self, items):
# separate parametrized setups

View File

@ -780,4 +780,182 @@ to a :ref:`conftest.py <conftest.py>` file or even separately installable
fixtures functions starts at test classes, then test modules, then
``conftest.py`` files and finally builtin and third party plugins.
Overriding fixtures on various levels
-------------------------------------
In relatively large test suite, you most likely need to ``override`` a ``global`` or ``root`` fixture with a ``locally``
defined one, keeping the test code readable and maintainable.
Override a fixture on a folder (conftest) level
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
test_something.py
# content of tests/test_something.py
def test_username(username):
assert username == 'username'
subfolder/
__init__.py
conftest.py
# content of tests/subfolder/conftest.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
test_something.py
# content of tests/subfolder/test_something.py
def test_username(username):
assert username == 'overridden-username'
As you can see, a fixture with the same name can be overridden for certain test folder level.
Note that the ``base`` or ``super`` fixture can be accessed from the ``overriding``
fixture easily - used in the example above.
Override a fixture on a test module level
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
@pytest.fixture
def username():
return 'username'
test_something.py
# content of tests/test_something.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-' + username
def test_username(username):
assert username == 'overridden-username'
test_something_else.py
# content of tests/test_something_else.py
import pytest
@pytest.fixture
def username(username):
return 'overridden-else-' + username
def test_username(username):
assert username == 'overridden-else-username'
In the example above, a fixture with the same name can be overridden for certain test module.
Override a fixture with direct test parametrization
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
@pytest.fixture
def other_username(username):
return 'other-' + username
test_something.py
# content of tests/test_something.py
import pytest
@pytest.mark.parametrize('username', ['directly-overridden-username'])
def test_username(username):
assert username == 'directly-overridden-username'
@pytest.mark.parametrize('username', ['directly-overridden-username-other'])
def test_username_other(other_username):
assert username == 'other-directly-overridden-username-other'
In the example above, a fixture value is overridden by the test parameter value. Note that the value of the fixture
can be overridden this way even if the test doesn't use it directly (doesn't mention it in the function prototype).
Override a parametrized fixture with non-parametrized one and vice versa
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture(params=['one', 'two', 'three'])
def parametrized_username(request):
return request.param
@pytest.fixture
def non_parametrized_username(request):
return 'username'
test_something.py
# content of tests/test_something.py
import pytest
@pytest.fixture
def parametrized_username():
return 'overridden-username'
@pytest.fixture(params=['one', 'two', 'three'])
def non_parametrized_username(request):
return request.param
def test_username(parametrized_username):
assert parametrized_username == 'overridden-username'
def test_parametrized_username(non_parametrized_username):
assert non_parametrized_username in ['one', 'two', 'three']
test_something_else.py
# content of tests/test_something_else.py
def test_username(parametrized_username):
assert parametrized_username in ['one', 'two', 'three']
def test_username(non_parametrized_username):
assert non_parametrized_username == 'username'
In the example above, a parametrized fixture is overridden with a non-parametrized version, and
a non-parametrized fixture is overridden with a parametrized version for certain test module.
The same applies for the test folder level obviously.

2
requirements-docs.txt Normal file
View File

@ -0,0 +1,2 @@
sphinx==1.2.3
hg+ssh://hg@bitbucket.org/RonnyPfannschmidt/regendoc#egg=regendoc

View File

@ -13,7 +13,8 @@ classifiers = ['Development Status :: 6 - Mature',
('Programming Language :: Python :: %s' % x) for x in
'2 2.6 2.7 3 3.2 3.3 3.4'.split()]
long_description = open('README.rst').read()
with open('README.rst') as fd:
long_description = fd.read()
def main():

View File

@ -393,14 +393,31 @@ class TestFunction:
return 'value'
@pytest.mark.parametrize('value',
['overrided'])
def test_overrided_via_param(value):
assert value == 'overrided'
['overridden'])
def test_overridden_via_param(value):
assert value == 'overridden'
""")
rec = testdir.inline_run()
rec.assertoutcome(passed=1)
def test_parametrize_overrides_parametrized_fixture(self, testdir):
"""Test parametrization when parameter overrides existing parametrized fixture with same name."""
testdir.makepyfile("""
import pytest
@pytest.fixture(params=[1, 2])
def value(request):
return request.param
@pytest.mark.parametrize('value',
['overridden'])
def test_overridden_via_param(value):
assert value == 'overridden'
""")
rec = testdir.inline_run()
rec.assertoutcome(passed=1)
def test_parametrize_with_mark(selfself, testdir):
items = testdir.getitems("""
import pytest

View File

@ -226,6 +226,114 @@ class TestFillFixtures:
result = testdir.runpytest()
assert result.ret == 0
def test_override_parametrized_fixture_conftest_module(self, testdir):
"""Test override of the parametrized fixture with non-parametrized one on the test module level."""
testdir.makeconftest("""
import pytest
@pytest.fixture(params=[1, 2, 3])
def spam(request):
return request.param
""")
testfile = testdir.makepyfile("""
import pytest
@pytest.fixture
def spam():
return 'spam'
def test_spam(spam):
assert spam == 'spam'
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"])
result = testdir.runpytest(testfile)
result.stdout.fnmatch_lines(["*1 passed*"])
def test_override_parametrized_fixture_conftest_conftest(self, testdir):
"""Test override of the parametrized fixture with non-parametrized one on the conftest level."""
testdir.makeconftest("""
import pytest
@pytest.fixture(params=[1, 2, 3])
def spam(request):
return request.param
""")
subdir = testdir.mkpydir('subdir')
subdir.join("conftest.py").write(py.code.Source("""
import pytest
@pytest.fixture
def spam():
return 'spam'
"""))
testfile = subdir.join("test_spam.py")
testfile.write(py.code.Source("""
def test_spam(spam):
assert spam == "spam"
"""))
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"])
result = testdir.runpytest(testfile)
result.stdout.fnmatch_lines(["*1 passed*"])
def test_override_non_parametrized_fixture_conftest_module(self, testdir):
"""Test override of the non-parametrized fixture with parametrized one on the test module level."""
testdir.makeconftest("""
import pytest
@pytest.fixture
def spam():
return 'spam'
""")
testfile = testdir.makepyfile("""
import pytest
@pytest.fixture(params=[1, 2, 3])
def spam(request):
return request.param
params = {'spam': 1}
def test_spam(spam):
assert spam == params['spam']
params['spam'] += 1
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*3 passed*"])
result = testdir.runpytest(testfile)
result.stdout.fnmatch_lines(["*3 passed*"])
def test_override_non_parametrized_fixture_conftest_conftest(self, testdir):
"""Test override of the non-parametrized fixture with parametrized one on the conftest level."""
testdir.makeconftest("""
import pytest
@pytest.fixture
def spam():
return 'spam'
""")
subdir = testdir.mkpydir('subdir')
subdir.join("conftest.py").write(py.code.Source("""
import pytest
@pytest.fixture(params=[1, 2, 3])
def spam(request):
return request.param
"""))
testfile = subdir.join("test_spam.py")
testfile.write(py.code.Source("""
params = {'spam': 1}
def test_spam(spam):
assert spam == params['spam']
params['spam'] += 1
"""))
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*3 passed*"])
result = testdir.runpytest(testfile)
result.stdout.fnmatch_lines(["*3 passed*"])
def test_autouse_fixture_plugin(self, testdir):
# A fixture from a plugin has no baseid set, which screwed up
# the autouse fixture handling.

View File

@ -136,7 +136,7 @@ commands=
minversion=2.0
plugins=pytester
#--pyargs --doctest-modules --ignore=.tox
addopts= -rxsX
addopts= -rxsX -vl
rsyncdirs=tox.ini pytest.py _pytest testing
python_files=test_*.py *_test.py testing/*/*.py
python_classes=Test Acceptance