merge factories/funcargs and setup functions into the new "fixture" document
This commit is contained in:
parent
3049af618c
commit
439cc1238f
|
@ -269,7 +269,11 @@ epub_copyright = u'2011, holger krekel et alii'
|
||||||
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {} # 'http://docs.python.org/': None}
|
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
|
||||||
|
'lib': ("http://docs.python.org/library/", None),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
#from sphinx.ext.autodoc import cut_lines
|
#from sphinx.ext.autodoc import cut_lines
|
||||||
#app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
|
#app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
|
||||||
|
|
|
@ -0,0 +1,681 @@
|
||||||
|
.. _xunitsetup:
|
||||||
|
.. _setup:
|
||||||
|
.. _fixture:
|
||||||
|
.. _`fixture functions`:
|
||||||
|
.. _`@pytest.fixture`:
|
||||||
|
|
||||||
|
pytest fixtures: modular, re-useable, flexible
|
||||||
|
========================================================
|
||||||
|
|
||||||
|
.. versionadded:: 2.0,2.3
|
||||||
|
|
||||||
|
.. _`funcargs`: funcargs.html
|
||||||
|
.. _`test parametrization`: funcargs.html#parametrizing-tests
|
||||||
|
.. _`unittest plugin`: plugin/unittest.html
|
||||||
|
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
||||||
|
.. _`general purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
|
||||||
|
.. _`django`: https://www.djangoproject.com/
|
||||||
|
.. _`pytest-django`: https://pypi.python.org/pytest-django
|
||||||
|
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection#Definition
|
||||||
|
|
||||||
|
pytest allows to provide and use test fixtures in a modular and flexible
|
||||||
|
manner, offering major improvements over the classic xUnit style of
|
||||||
|
setup/teardown functions. The `general purpose of test fixtures`_ is to
|
||||||
|
provide a fixed baseline upon which tests can reliably and
|
||||||
|
repeatedly execute. With pytest, fixtures are implemented by
|
||||||
|
**fixture functions** which may return a fixture object, put extra
|
||||||
|
attributes on test classes or perform side effects. The name of a
|
||||||
|
fixture function is significant and is used for invoking or activating it.
|
||||||
|
|
||||||
|
**Test functions can receive fixture objects by naming them as an input
|
||||||
|
argument.** For each argument name, a matching fixture
|
||||||
|
function will provide a fixture object. This mechanism has been
|
||||||
|
introduced with pytest-2.0 and is also called the **funcarg mechanism**.
|
||||||
|
It allows test functions to easily receive and work against specific
|
||||||
|
pre-initialized application objects without having to care about the
|
||||||
|
details of setup/cleanup procedures. This mechanism is a prime example of
|
||||||
|
`dependency injection`_ where fixture functions take the role of the
|
||||||
|
*injector* and test functions are the *consumers* of fixture objects.
|
||||||
|
With pytest-2.3 this mechanism has been much improved to help with
|
||||||
|
sharing and parametrizing fixtures across test runs.
|
||||||
|
|
||||||
|
**Test classes, modules or whole projects can declare a need for
|
||||||
|
one or more fixtures**. All required fixture functions will execute
|
||||||
|
before a test from the specifying context executes. They will
|
||||||
|
typically not provide a fixture object but rather perform side effects
|
||||||
|
like reading or preparing default config settings and pre-initializing
|
||||||
|
an application. For example, the Django_ project requires database
|
||||||
|
initialization to be able to import from and use its model objects.
|
||||||
|
Plugins like `pytest-django`_ provide baseline fixtures which your
|
||||||
|
project can then easily depend or extend on.
|
||||||
|
|
||||||
|
**Fixtures can be shared throughout a test session, module or class.**.
|
||||||
|
By means of a "scope" declaration on a fixture function, it will
|
||||||
|
only be invoked once per the specified scope. Sharing expensive application
|
||||||
|
object setups between tests typically helps to speed up test runs.
|
||||||
|
Typical examples are the setup of test databases or establishing
|
||||||
|
required subprocesses or network connections.
|
||||||
|
|
||||||
|
**Fixture functions have controlled visilibity** which depends on where they
|
||||||
|
are defined. If they are defined on a test class, only its test methods
|
||||||
|
may use it. A fixture defined in a module can only be used
|
||||||
|
from that test module. A fixture defined in a conftest.py file
|
||||||
|
can only be used by the tests below the directory of that file.
|
||||||
|
Lastly plugins can define fixtures which are available across all
|
||||||
|
projects.
|
||||||
|
|
||||||
|
**Fixture functions can interact with the requesting testcontext**. By
|
||||||
|
accepting a special ``request`` object, fixture functions can introspect
|
||||||
|
the function, class or module for which they are invoked and can
|
||||||
|
optionally register cleanup functions which are called when the last
|
||||||
|
test finished execution. A good example is `pytest-timeout`_ which
|
||||||
|
allows to limit the execution time of a test, and will read the
|
||||||
|
according parameter from a test function or from project-wide setting.
|
||||||
|
|
||||||
|
**Fixture functions can be parametrized** in which case they will be called
|
||||||
|
multiple times, each time executing the set of dependent tests, i. e. the
|
||||||
|
tests that depend on this fixture. Test functions do usually not need
|
||||||
|
to be aware of their re-running. Fixture parametrization helps to
|
||||||
|
write functional tests for components which themselves can be
|
||||||
|
configured in multiple ways.
|
||||||
|
|
||||||
|
|
||||||
|
Basic funcarg fixture example
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 2.3
|
||||||
|
|
||||||
|
|
||||||
|
Let's look at a simple self-contained test module containing a module
|
||||||
|
visible fixture function and a test function using the provided fixture::
|
||||||
|
|
||||||
|
# content of ./test_simplefactory.py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def myfuncarg():
|
||||||
|
return 42
|
||||||
|
|
||||||
|
def test_function(myfuncarg):
|
||||||
|
assert myfuncarg == 17
|
||||||
|
|
||||||
|
Here, the ``test_function`` needs an object named ``myfuncarg`` and thus
|
||||||
|
py.test will discover and call the ``@pytest.fixture`` marked ``myfuncarg``
|
||||||
|
factory function. Running the tests looks like this::
|
||||||
|
|
||||||
|
$ py.test test_simplefactory.py
|
||||||
|
=========================== test session starts ============================
|
||||||
|
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
|
||||||
|
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||||
|
collecting ... collected 1 items
|
||||||
|
|
||||||
|
test_simplefactory.py F
|
||||||
|
|
||||||
|
================================= FAILURES =================================
|
||||||
|
______________________________ test_function _______________________________
|
||||||
|
|
||||||
|
myfuncarg = 42
|
||||||
|
|
||||||
|
def test_function(myfuncarg):
|
||||||
|
> assert myfuncarg == 17
|
||||||
|
E assert 42 == 17
|
||||||
|
|
||||||
|
test_simplefactory.py:8: AssertionError
|
||||||
|
========================= 1 failed in 0.01 seconds =========================
|
||||||
|
|
||||||
|
This shows that the test function was called with a ``myfuncarg``
|
||||||
|
argument value of ``42`` and the assert fails as expected. Here is
|
||||||
|
how py.test comes to call the test function this way:
|
||||||
|
|
||||||
|
1. py.test :ref:`finds <test discovery>` the ``test_function`` because
|
||||||
|
of the ``test_`` prefix. The test function needs a function argument
|
||||||
|
named ``myfuncarg``. A matching factory function is discovered by
|
||||||
|
looking for a fixture function named ``myfuncarg``.
|
||||||
|
|
||||||
|
2. ``myfuncarg()`` is called to create a value ``42``.
|
||||||
|
|
||||||
|
3. ``test_function(42)`` is now called and results in the above
|
||||||
|
reported exception because of the assertion mismatch.
|
||||||
|
|
||||||
|
Note that if you misspell a function argument or want
|
||||||
|
to use one that isn't available, you'll see an error
|
||||||
|
with a list of available function arguments.
|
||||||
|
|
||||||
|
.. Note::
|
||||||
|
|
||||||
|
You can always issue::
|
||||||
|
|
||||||
|
py.test --fixtures test_simplefactory.py
|
||||||
|
|
||||||
|
to see available fixtures.
|
||||||
|
|
||||||
|
In versions prior to 2.3 there was no @pytest.fixture marker
|
||||||
|
and you had to instead use a magic ``pytest_funcarg__NAME`` prefix
|
||||||
|
for the fixture factory. This remains and will remain supported
|
||||||
|
but is not advertised as the primary means of declaring fixture
|
||||||
|
functions.
|
||||||
|
|
||||||
|
|
||||||
|
Creating and using a session-shared fixture
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
.. regendoc:wipe
|
||||||
|
|
||||||
|
Here is a simple example of a fixture function creating a shared
|
||||||
|
``smtplib.SMTP`` connection fixture which test functions from
|
||||||
|
test modules below the directory of a ``conftest.py`` file may use::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
import pytest
|
||||||
|
import smtplib
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def smtp():
|
||||||
|
return smtplib.SMTP("merlinux.eu")
|
||||||
|
|
||||||
|
The name of the fixture is ``smtp`` and you can access its result by
|
||||||
|
listing the name ``smtp`` as an input parameter in any test or setup
|
||||||
|
function::
|
||||||
|
|
||||||
|
# content of test_module.py
|
||||||
|
def test_ehlo(smtp):
|
||||||
|
response = smtp.ehlo()
|
||||||
|
assert response[0] == 250
|
||||||
|
assert "merlinux" in response[1]
|
||||||
|
assert 0 # for demo purposes
|
||||||
|
|
||||||
|
def test_noop(smtp):
|
||||||
|
response = smtp.noop()
|
||||||
|
assert response[0] == 250
|
||||||
|
assert 0 # for demo purposes
|
||||||
|
|
||||||
|
We deliberately insert failing ``assert 0`` statements in order to
|
||||||
|
inspect what is going on and can now run the tests::
|
||||||
|
|
||||||
|
$ py.test -q test_module.py
|
||||||
|
collecting ... collected 2 items
|
||||||
|
FF
|
||||||
|
================================= FAILURES =================================
|
||||||
|
________________________________ test_ehlo _________________________________
|
||||||
|
|
||||||
|
smtp = <smtplib.SMTP instance at 0x31bce18>
|
||||||
|
|
||||||
|
def test_ehlo(smtp):
|
||||||
|
response = smtp.ehlo()
|
||||||
|
assert response[0] == 250
|
||||||
|
assert "merlinux" in response[1]
|
||||||
|
> assert 0 # for demo purposes
|
||||||
|
E assert 0
|
||||||
|
|
||||||
|
test_module.py:5: AssertionError
|
||||||
|
________________________________ test_noop _________________________________
|
||||||
|
|
||||||
|
smtp = <smtplib.SMTP instance at 0x31bce18>
|
||||||
|
|
||||||
|
def test_noop(smtp):
|
||||||
|
response = smtp.noop()
|
||||||
|
assert response[0] == 250
|
||||||
|
> assert 0 # for demo purposes
|
||||||
|
E assert 0
|
||||||
|
|
||||||
|
test_module.py:10: AssertionError
|
||||||
|
2 failed in 0.26 seconds
|
||||||
|
|
||||||
|
you see the two ``assert 0`` failing and can also see that
|
||||||
|
the same (session-scoped) object was passed into the two test functions
|
||||||
|
because pytest shows the incoming arguments in the traceback.
|
||||||
|
|
||||||
|
Adding a finalizer to a fixture
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
Further extending the ``smtp`` example, we now want to properly
|
||||||
|
close a smtp server connection after the last test using it
|
||||||
|
has been run. We can do this by calling the ``request.addfinalizer()``
|
||||||
|
helper::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
import pytest
|
||||||
|
import smtplib
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def smtp(request):
|
||||||
|
smtp = smtplib.SMTP("merlinux.eu")
|
||||||
|
def fin():
|
||||||
|
print ("finalizing %s" % smtp)
|
||||||
|
smtp.close()
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
return smtp
|
||||||
|
|
||||||
|
The registered ``fin`` function will be called when the last test
|
||||||
|
using it has executed::
|
||||||
|
|
||||||
|
$ py.test -s -q --tb=no
|
||||||
|
collecting ... collected 4 items
|
||||||
|
FFFF
|
||||||
|
4 failed in 6.40 seconds
|
||||||
|
finalizing <smtplib.SMTP instance at 0x125d3b0>
|
||||||
|
|
||||||
|
We see that the ``smtp`` instance is finalized after all
|
||||||
|
tests executed. If we had specified ``scope='function'``
|
||||||
|
then fixture setup and cleanup would occur around each
|
||||||
|
single test.
|
||||||
|
|
||||||
|
Parametrizing a session-shared funcarg resource
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Extending the previous example, we can flag the fixture to create
|
||||||
|
two ``smtp`` fixture instances which will cause all tests using the
|
||||||
|
fixture to run twice. The fixture function gets
|
||||||
|
access to each parameter through the special `request`_ object::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
import pytest
|
||||||
|
import smtplib
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session",
|
||||||
|
params=["merlinux.eu", "mail.python.org"])
|
||||||
|
def smtp(request):
|
||||||
|
smtp = smtplib.SMTP(request.param)
|
||||||
|
def fin():
|
||||||
|
print ("finalizing %s" % smtp)
|
||||||
|
smtp.close()
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
return smtp
|
||||||
|
|
||||||
|
The main change is the declaration of ``params``, a list of values
|
||||||
|
for each of which the fixture function will execute and can access
|
||||||
|
a value via ``request.param``. No test function code needs to change.
|
||||||
|
So let's just do another run::
|
||||||
|
|
||||||
|
$ py.test -q
|
||||||
|
collecting ... collected 4 items
|
||||||
|
FFFF
|
||||||
|
================================= FAILURES =================================
|
||||||
|
__________________________ test_ehlo[merlinux.eu] __________________________
|
||||||
|
|
||||||
|
smtp = <smtplib.SMTP instance at 0x28dc5a8>
|
||||||
|
|
||||||
|
def test_ehlo(smtp):
|
||||||
|
response = smtp.ehlo()
|
||||||
|
assert response[0] == 250
|
||||||
|
assert "merlinux" in response[1]
|
||||||
|
> assert 0 # for demo purposes
|
||||||
|
E assert 0
|
||||||
|
|
||||||
|
test_module.py:5: AssertionError
|
||||||
|
__________________________ test_noop[merlinux.eu] __________________________
|
||||||
|
|
||||||
|
smtp = <smtplib.SMTP instance at 0x28dc5a8>
|
||||||
|
|
||||||
|
def test_noop(smtp):
|
||||||
|
response = smtp.noop()
|
||||||
|
assert response[0] == 250
|
||||||
|
> assert 0 # for demo purposes
|
||||||
|
E assert 0
|
||||||
|
|
||||||
|
test_module.py:10: AssertionError
|
||||||
|
________________________ test_ehlo[mail.python.org] ________________________
|
||||||
|
|
||||||
|
smtp = <smtplib.SMTP instance at 0x28e3e18>
|
||||||
|
|
||||||
|
def test_ehlo(smtp):
|
||||||
|
response = smtp.ehlo()
|
||||||
|
assert response[0] == 250
|
||||||
|
> assert "merlinux" in response[1]
|
||||||
|
E assert 'merlinux' in 'mail.python.org\nSIZE 10240000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN'
|
||||||
|
|
||||||
|
test_module.py:4: AssertionError
|
||||||
|
________________________ test_noop[mail.python.org] ________________________
|
||||||
|
|
||||||
|
smtp = <smtplib.SMTP instance at 0x28e3e18>
|
||||||
|
|
||||||
|
def test_noop(smtp):
|
||||||
|
response = smtp.noop()
|
||||||
|
assert response[0] == 250
|
||||||
|
> assert 0 # for demo purposes
|
||||||
|
E assert 0
|
||||||
|
|
||||||
|
test_module.py:10: AssertionError
|
||||||
|
4 failed in 6.17 seconds
|
||||||
|
|
||||||
|
We now get four failures because we are running the two tests twice with
|
||||||
|
different ``smtp`` fixture instances. Note that with the
|
||||||
|
``mail.python.org`` connection the second test fails in ``test_ehlo``
|
||||||
|
because it expects a specific server string.
|
||||||
|
|
||||||
|
We also see that the two ``smtp`` instances are finalized appropriately.
|
||||||
|
|
||||||
|
Looking at test collection without running tests
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
You can also look at the tests which pytest collects without running them::
|
||||||
|
|
||||||
|
$ py.test --collectonly
|
||||||
|
=========================== test session starts ============================
|
||||||
|
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
|
||||||
|
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||||
|
collecting ... collected 4 items
|
||||||
|
<Module 'test_module.py'>
|
||||||
|
<Function 'test_ehlo[merlinux.eu]'>
|
||||||
|
<Function 'test_noop[merlinux.eu]'>
|
||||||
|
<Function 'test_ehlo[mail.python.org]'>
|
||||||
|
<Function 'test_noop[mail.python.org]'>
|
||||||
|
|
||||||
|
============================= in 0.01 seconds =============================
|
||||||
|
|
||||||
|
Our fixture parameters show up in the test id of the test functions.
|
||||||
|
Note that pytest orders your test run by resource usage, minimizing
|
||||||
|
the number of active resources at any given time.
|
||||||
|
|
||||||
|
|
||||||
|
.. _`interdependent fixtures`:
|
||||||
|
|
||||||
|
Interdepdendent fixtures
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
You can not only use fixtures in test functions but fixture functions
|
||||||
|
can use other fixtures themselves. This contributes to a modular design
|
||||||
|
of your fixtures and allows re-use of framework-specific fixtures across
|
||||||
|
many projects. As a simple example, we can extend the previous example
|
||||||
|
and instantiate an object ``app`` where we stick the already defined
|
||||||
|
``smtp`` resource into it::
|
||||||
|
|
||||||
|
# content of test_appsetup.py
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class App:
|
||||||
|
def __init__(self, smtp):
|
||||||
|
self.smtp = smtp
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def app(smtp):
|
||||||
|
return App(smtp)
|
||||||
|
|
||||||
|
def test_smtp_exists(app):
|
||||||
|
assert app.smtp
|
||||||
|
|
||||||
|
Here we declare an ``app`` fixture which receives the previously defined
|
||||||
|
``smtp`` fixture and instantiates an ``App`` object with it. Let's run it::
|
||||||
|
|
||||||
|
$ py.test -v test_appsetup.py
|
||||||
|
=========================== test session starts ============================
|
||||||
|
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
|
||||||
|
cachedir: /home/hpk/tmp/doc-exec-423/.cache
|
||||||
|
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||||
|
collecting ... collected 2 items
|
||||||
|
|
||||||
|
test_appsetup.py:12: test_exists[merlinux.eu] PASSED
|
||||||
|
test_appsetup.py:12: test_exists[mail.python.org] PASSED
|
||||||
|
|
||||||
|
========================= 2 passed in 6.82 seconds =========================
|
||||||
|
|
||||||
|
Due to the parametrization of ``smtp`` the test will run twice with two
|
||||||
|
different ``App`` instances and respective smtp servers. There is no
|
||||||
|
need for the ``app`` fixture to be aware of the ``smtp`` parametrization
|
||||||
|
as pytest will fully analyse the fixture dependency graph. Note also,
|
||||||
|
that the ``app`` fixture has a scope of ``module`` but uses a
|
||||||
|
session-scoped ``smtp``: it is fine for fixtures to use "broader" scoped
|
||||||
|
fixtures but not the other way round: A session-scoped fixture could
|
||||||
|
not use a module-scoped one in a meaningful way.
|
||||||
|
|
||||||
|
.. _`automatic per-resource grouping`:
|
||||||
|
|
||||||
|
Automatic grouping of tests by fixture instances
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
.. regendoc: wipe
|
||||||
|
|
||||||
|
pytest minimizes the number of active fixtures during test runs.
|
||||||
|
If you have a parametrized fixture, then all the tests using it will
|
||||||
|
first execute with one instance and then finalizers are called
|
||||||
|
before the next fixture instance is created. Among other things,
|
||||||
|
this eases testing of applications which create and use global state.
|
||||||
|
|
||||||
|
The following example uses two parametrized funcargs, one of which is
|
||||||
|
scoped on a per-module basis, and all the functions perform ``print`` call s
|
||||||
|
to show the flow of calls::
|
||||||
|
|
||||||
|
# content of test_module.py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module", params=["mod1", "mod2"])
|
||||||
|
def modarg(request):
|
||||||
|
param = request.param
|
||||||
|
print "create", param
|
||||||
|
def fin():
|
||||||
|
print "fin", param
|
||||||
|
request.addfinalizer(fin)
|
||||||
|
return param
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function", params=[1,2])
|
||||||
|
def otherarg(request):
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
def test_0(otherarg):
|
||||||
|
print " test0", otherarg
|
||||||
|
def test_1(modarg):
|
||||||
|
print " test1", modarg
|
||||||
|
def test_2(otherarg, modarg):
|
||||||
|
print " test2", otherarg, modarg
|
||||||
|
|
||||||
|
Let's run the tests in verbose mode and with looking at the print-output::
|
||||||
|
|
||||||
|
$ py.test -v -s test_module.py
|
||||||
|
=========================== test session starts ============================
|
||||||
|
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
|
||||||
|
cachedir: /home/hpk/tmp/doc-exec-423/.cache
|
||||||
|
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
||||||
|
collecting ... collected 8 items
|
||||||
|
|
||||||
|
test_module.py:16: test_0[1] PASSED
|
||||||
|
test_module.py:16: test_0[2] PASSED
|
||||||
|
test_module.py:18: test_1[mod1] PASSED
|
||||||
|
test_module.py:20: test_2[1-mod1] PASSED
|
||||||
|
test_module.py:20: test_2[2-mod1] PASSED
|
||||||
|
test_module.py:18: test_1[mod2] PASSED
|
||||||
|
test_module.py:20: test_2[1-mod2] PASSED
|
||||||
|
test_module.py:20: test_2[2-mod2] PASSED
|
||||||
|
|
||||||
|
========================= 8 passed in 0.02 seconds =========================
|
||||||
|
test0 1
|
||||||
|
test0 2
|
||||||
|
create mod1
|
||||||
|
test1 mod1
|
||||||
|
test2 1 mod1
|
||||||
|
test2 2 mod1
|
||||||
|
fin mod1
|
||||||
|
create mod2
|
||||||
|
test1 mod2
|
||||||
|
test2 1 mod2
|
||||||
|
test2 2 mod2
|
||||||
|
fin mod2
|
||||||
|
|
||||||
|
You can see that the parametrized module-scoped ``modarg`` resource caused
|
||||||
|
an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed
|
||||||
|
before the ``mod2`` resource was setup.
|
||||||
|
|
||||||
|
|
||||||
|
Marking test classes, modules, projects with required fixtures
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
.. regendoc:wipe
|
||||||
|
|
||||||
|
Sometimes test functions do not directly get access to a fixture object.
|
||||||
|
For example, each test in a test class may require to operate with an
|
||||||
|
empty directory as the current working directory. Here is how you can
|
||||||
|
can use the standard :ref:`tempfile <lib:tempfile>` and pytest fixtures
|
||||||
|
to achieve it. We separate the creation of the fixture into
|
||||||
|
a conftest.py file::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def cleandir():
|
||||||
|
newpath = tempfile.mkdtemp()
|
||||||
|
os.chdir(newpath)
|
||||||
|
|
||||||
|
and declare its use in a test module via a ``needs`` marker::
|
||||||
|
|
||||||
|
# content of test_setenv.py
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.needsfixtures("cleandir")
|
||||||
|
class TestDirectoryInit:
|
||||||
|
def test_cwd_starts_empty(self):
|
||||||
|
assert os.listdir(os.getcwd()) == []
|
||||||
|
with open("myfile", "w") as f:
|
||||||
|
f.write("hello")
|
||||||
|
|
||||||
|
def test_cwd_again_starts_empty(self):
|
||||||
|
assert os.listdir(os.getcwd()) == []
|
||||||
|
|
||||||
|
Due to the ``needs`` class marker, the ``cleandir`` fixture
|
||||||
|
will be required for the execution of each of the test methods, just as if
|
||||||
|
you specified a "cleandir" function argument to each of them. Let's run it
|
||||||
|
to verify our fixture is activated::
|
||||||
|
|
||||||
|
$ py.test -q
|
||||||
|
collecting ... collected 2 items
|
||||||
|
.
|
||||||
|
2 passed in 0.01 seconds
|
||||||
|
|
||||||
|
You may specify the need for multiple fixtures::
|
||||||
|
|
||||||
|
@pytest.mark.needsfixtures("cleandir", "anotherfixture")
|
||||||
|
|
||||||
|
and you may specify fixture needs at the test module level, using
|
||||||
|
a generic feature of the mark mechanism::
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.needsfixtures("cleandir")
|
||||||
|
|
||||||
|
Lastly you can put fixtures required by all tests in your project
|
||||||
|
into an ini-file::
|
||||||
|
|
||||||
|
# content of pytest.ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
needsfixtures = cleandir
|
||||||
|
|
||||||
|
Implicit fixtures at class/module/directory/global level
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
.. regendoc:wipe
|
||||||
|
|
||||||
|
Occasionally, you may want to have fixtures get invoked automatically
|
||||||
|
without any ``needs`` reference. Also, if you are used to the classical
|
||||||
|
xUnit setup/teardown functions you may have gotten used to fixture
|
||||||
|
functions executing always. As a practical example,
|
||||||
|
suppose we have a database fixture which has a begin/rollback/commit
|
||||||
|
architecture and we want to surround each test method by a transaction
|
||||||
|
and a rollback. Here is a dummy self-contained implementation::
|
||||||
|
|
||||||
|
# content of test_db_transact.py
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
class db:
|
||||||
|
def __init__(self):
|
||||||
|
self.intransaction = False
|
||||||
|
def begin(self):
|
||||||
|
self.intransaction = True
|
||||||
|
def rollback(Self):
|
||||||
|
self.intransaction = False
|
||||||
|
|
||||||
|
class TestClass:
|
||||||
|
@pytest.fixture(auto=True)
|
||||||
|
def transact(self, request, db):
|
||||||
|
db.begin()
|
||||||
|
request.addfinalizer(db.rollback)
|
||||||
|
|
||||||
|
def test_method1(self, db):
|
||||||
|
assert db.intransaction
|
||||||
|
|
||||||
|
def test_method2(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
The class-level ``transact`` fixture is marked with *auto=true* which will
|
||||||
|
mark all test methods in the class as needing the fixture.
|
||||||
|
|
||||||
|
Here is how this maps to module, project and cross-project scopes:
|
||||||
|
|
||||||
|
- if an automatic fixture was defined in a test module, all its test
|
||||||
|
functions would automatically invoke it.
|
||||||
|
|
||||||
|
- if defined in a conftest.py file then all tests in all test
|
||||||
|
modules belows its directory will invoke the fixture.
|
||||||
|
|
||||||
|
- lastly, and **please use that with care**: if you define an automatic
|
||||||
|
fixture in a plugin, it will be invoked for all tests in all projects
|
||||||
|
where the plugin is installed. This can be useful if a fixture only
|
||||||
|
anyway works in the presence of certain settings in the ini-file. Such
|
||||||
|
a global fixture should thus quickly determine if it should do
|
||||||
|
any work and avoid expensive imports or computation otherwise.
|
||||||
|
|
||||||
|
Note that the above ``transact`` fixture may very well be something that
|
||||||
|
you want to make available in your project without having each test function
|
||||||
|
in your project automatically using it. The canonical way to do that is to put
|
||||||
|
the transact definition into a conftest.py file without using ``auto``::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
@pytest.fixture()
|
||||||
|
def transact(self, request, db):
|
||||||
|
db.begin()
|
||||||
|
request.addfinalizer(db.rollback)
|
||||||
|
|
||||||
|
and then have a TestClass using it by declaring the need::
|
||||||
|
|
||||||
|
@pytest.mark.needsfixtures("transact")
|
||||||
|
class TestClass:
|
||||||
|
def test_method1(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
While all test methods in this TestClass will thus use the transaction
|
||||||
|
fixture, other test classes will not unless they state the need.
|
||||||
|
|
||||||
|
.. currentmodule:: _pytest.python
|
||||||
|
|
||||||
|
.. _`@pytest.fixture`:
|
||||||
|
|
||||||
|
``@pytest.fixture``: marking a fixture function
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
The ``@pytest.fixture`` marker allows to
|
||||||
|
|
||||||
|
* mark a function as a factory for fixtures, useable by test and other
|
||||||
|
fixture functions
|
||||||
|
|
||||||
|
* declare a scope which determines the level of caching, i.e. how often
|
||||||
|
the factory will be called. Valid scopes are ``session``, ``module``,
|
||||||
|
``class`` and ``function``.
|
||||||
|
|
||||||
|
* define a list of parameters in order to run dependent tests multiple
|
||||||
|
times with different fixtures
|
||||||
|
|
||||||
|
.. _`request`:
|
||||||
|
|
||||||
|
``request``: interacting with test invocation context
|
||||||
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
The ``request`` object may be received by fixture functions
|
||||||
|
and provides methods to:
|
||||||
|
|
||||||
|
* to inspect attributes of the requesting test context, such as
|
||||||
|
``function``, ``cls``, ``module``, ``session`` and the pytest
|
||||||
|
``config`` object. A request object passed to a parametrized factory
|
||||||
|
will also carry a ``request.param`` object (A parametrized factory and
|
||||||
|
all of its dependent tests will be called with each of the factory-specified
|
||||||
|
``params``).
|
||||||
|
|
||||||
|
* to add finalizers/teardowns to be invoked when the last
|
||||||
|
test of the requesting test context executes
|
||||||
|
|
||||||
|
.. autoclass:: _pytest.python.FuncargRequest()
|
||||||
|
:members:
|
||||||
|
|
|
@ -159,366 +159,6 @@ to a :ref:`conftest.py <conftest.py>` file or even separately installable
|
||||||
funcarg factories starts at test classes, then test modules, then
|
funcarg factories starts at test classes, then test modules, then
|
||||||
``conftest.py`` files and finally builtin and 3-rd party plugins.
|
``conftest.py`` files and finally builtin and 3-rd party plugins.
|
||||||
|
|
||||||
Creating and using a session-shared funcarg
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
.. regendoc:wipe
|
|
||||||
|
|
||||||
.. versionadded:: 2.3
|
|
||||||
|
|
||||||
The `@pytest.factory`_ marker allows to
|
|
||||||
|
|
||||||
* mark a function as a factory for resources, useable by test and setup
|
|
||||||
functions
|
|
||||||
|
|
||||||
* define parameters in order to run tests multiple times with
|
|
||||||
different resource instances
|
|
||||||
|
|
||||||
* declare a scope which determines the level of caching, i.e. how often
|
|
||||||
the factory will be called. Valid scopes are ``session``, ``module``,
|
|
||||||
``class`` and ``function``.
|
|
||||||
|
|
||||||
Here is a simple example of a factory creating a shared ``smtplib.SMTP``
|
|
||||||
connection resource which test functions then may use across the whole
|
|
||||||
test session::
|
|
||||||
|
|
||||||
# content of conftest.py
|
|
||||||
import pytest
|
|
||||||
import smtplib
|
|
||||||
|
|
||||||
@pytest.factory(scope="session")
|
|
||||||
def smtp(request):
|
|
||||||
return smtplib.SMTP("merlinux.eu")
|
|
||||||
|
|
||||||
The name of the factory is ``smtp`` (the factory function name)
|
|
||||||
and you can access its result by listing the name ``smtp`` as
|
|
||||||
an input parameter in any test or setup function::
|
|
||||||
|
|
||||||
# content of test_module.py
|
|
||||||
def test_ehlo(smtp):
|
|
||||||
response = smtp.ehlo()
|
|
||||||
assert response[0] == 250
|
|
||||||
assert "merlinux" in response[1]
|
|
||||||
assert 0 # for demo purposes
|
|
||||||
|
|
||||||
def test_noop(smtp):
|
|
||||||
response = smtp.noop()
|
|
||||||
assert response[0] == 250
|
|
||||||
assert 0 # for demo purposes
|
|
||||||
|
|
||||||
We deliberately insert failing ``assert 0`` statements in order to
|
|
||||||
inspect what is going on and can now run the tests::
|
|
||||||
|
|
||||||
$ py.test -q test_module.py
|
|
||||||
collecting ... collected 2 items
|
|
||||||
FF
|
|
||||||
================================= FAILURES =================================
|
|
||||||
________________________________ test_ehlo _________________________________
|
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x31bce18>
|
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
|
||||||
response = smtp.ehlo()
|
|
||||||
assert response[0] == 250
|
|
||||||
assert "merlinux" in response[1]
|
|
||||||
> assert 0 # for demo purposes
|
|
||||||
E assert 0
|
|
||||||
|
|
||||||
test_module.py:5: AssertionError
|
|
||||||
________________________________ test_noop _________________________________
|
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x31bce18>
|
|
||||||
|
|
||||||
def test_noop(smtp):
|
|
||||||
response = smtp.noop()
|
|
||||||
assert response[0] == 250
|
|
||||||
> assert 0 # for demo purposes
|
|
||||||
E assert 0
|
|
||||||
|
|
||||||
test_module.py:10: AssertionError
|
|
||||||
2 failed in 0.26 seconds
|
|
||||||
|
|
||||||
you see the two ``assert 0`` failing and can also see that
|
|
||||||
the same (session-scoped) object was passed into the two test functions.
|
|
||||||
|
|
||||||
|
|
||||||
Parametrizing a session-shared funcarg resource
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
Extending the previous example, we can flag the factory to create
|
|
||||||
two ``smtp`` values which will cause all tests using it to
|
|
||||||
run twice with two different values. The factory function gets
|
|
||||||
access to each parameter through the special `request`_ object::
|
|
||||||
|
|
||||||
# content of conftest.py
|
|
||||||
import pytest
|
|
||||||
import smtplib
|
|
||||||
|
|
||||||
@pytest.factory(scope="session",
|
|
||||||
params=["merlinux.eu", "mail.python.org"])
|
|
||||||
def smtp(request):
|
|
||||||
return smtplib.SMTP(request.param)
|
|
||||||
|
|
||||||
The main change is the definition of a ``params`` list in the
|
|
||||||
``factory``-marker and the ``request.param`` access within the
|
|
||||||
factory function. No test function code needs to change.
|
|
||||||
So let's just do another run::
|
|
||||||
|
|
||||||
$ py.test -q
|
|
||||||
collecting ... collected 4 items
|
|
||||||
FFFF
|
|
||||||
================================= FAILURES =================================
|
|
||||||
__________________________ test_ehlo[merlinux.eu] __________________________
|
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x28dc5a8>
|
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
|
||||||
response = smtp.ehlo()
|
|
||||||
assert response[0] == 250
|
|
||||||
assert "merlinux" in response[1]
|
|
||||||
> assert 0 # for demo purposes
|
|
||||||
E assert 0
|
|
||||||
|
|
||||||
test_module.py:5: AssertionError
|
|
||||||
__________________________ test_noop[merlinux.eu] __________________________
|
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x28dc5a8>
|
|
||||||
|
|
||||||
def test_noop(smtp):
|
|
||||||
response = smtp.noop()
|
|
||||||
assert response[0] == 250
|
|
||||||
> assert 0 # for demo purposes
|
|
||||||
E assert 0
|
|
||||||
|
|
||||||
test_module.py:10: AssertionError
|
|
||||||
________________________ test_ehlo[mail.python.org] ________________________
|
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x28e3e18>
|
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
|
||||||
response = smtp.ehlo()
|
|
||||||
assert response[0] == 250
|
|
||||||
> assert "merlinux" in response[1]
|
|
||||||
E assert 'merlinux' in 'mail.python.org\nSIZE 10240000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN'
|
|
||||||
|
|
||||||
test_module.py:4: AssertionError
|
|
||||||
________________________ test_noop[mail.python.org] ________________________
|
|
||||||
|
|
||||||
smtp = <smtplib.SMTP instance at 0x28e3e18>
|
|
||||||
|
|
||||||
def test_noop(smtp):
|
|
||||||
response = smtp.noop()
|
|
||||||
assert response[0] == 250
|
|
||||||
> assert 0 # for demo purposes
|
|
||||||
E assert 0
|
|
||||||
|
|
||||||
test_module.py:10: AssertionError
|
|
||||||
4 failed in 6.17 seconds
|
|
||||||
|
|
||||||
We get four failures because we are running the two tests twice with
|
|
||||||
different ``smtp`` instantiations as defined on the factory.
|
|
||||||
Note that with the ``mail.python.org`` connection the second test
|
|
||||||
fails in ``test_ehlo`` because it expects a specific server string.
|
|
||||||
|
|
||||||
Adding a finalizer to the parametrized resource
|
|
||||||
--------------------------------------------------------
|
|
||||||
|
|
||||||
Further extending the ``smtp`` example, we now want to properly
|
|
||||||
close a smtp server connection after the last test using it
|
|
||||||
has been run. We can do this by calling the ``request.addfinalizer()``
|
|
||||||
helper::
|
|
||||||
|
|
||||||
# content of conftest.py
|
|
||||||
import pytest
|
|
||||||
import smtplib
|
|
||||||
|
|
||||||
@pytest.factory(scope="session",
|
|
||||||
params=["merlinux.eu", "mail.python.org"])
|
|
||||||
def smtp(request):
|
|
||||||
smtp = smtplib.SMTP(request.param)
|
|
||||||
def fin():
|
|
||||||
print ("finalizing %s" % smtp)
|
|
||||||
smtp.close()
|
|
||||||
request.addfinalizer(fin)
|
|
||||||
return smtp
|
|
||||||
|
|
||||||
We also add a print call and then run py.test without the default
|
|
||||||
output capturing and disabled traceback reporting::
|
|
||||||
|
|
||||||
$ py.test -s -q --tb=no
|
|
||||||
collecting ... collected 4 items
|
|
||||||
FFFF
|
|
||||||
4 failed in 6.40 seconds
|
|
||||||
finalizing <smtplib.SMTP instance at 0x125d3b0>
|
|
||||||
finalizing <smtplib.SMTP instance at 0x1265b90>
|
|
||||||
|
|
||||||
We see that the two ``smtp`` instances are finalized appropriately.
|
|
||||||
|
|
||||||
Looking at test collection without running tests
|
|
||||||
------------------------------------------------------
|
|
||||||
|
|
||||||
You can also look at what tests pytest collects without running them::
|
|
||||||
|
|
||||||
$ py.test --collectonly
|
|
||||||
=========================== test session starts ============================
|
|
||||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
|
|
||||||
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
|
||||||
collecting ... collected 4 items
|
|
||||||
<Module 'test_module.py'>
|
|
||||||
<Function 'test_ehlo[merlinux.eu]'>
|
|
||||||
<Function 'test_noop[merlinux.eu]'>
|
|
||||||
<Function 'test_ehlo[mail.python.org]'>
|
|
||||||
<Function 'test_noop[mail.python.org]'>
|
|
||||||
|
|
||||||
============================= in 0.01 seconds =============================
|
|
||||||
|
|
||||||
Note that pytest orders your test run by resource usage, minimizing
|
|
||||||
the number of active resources at any given time.
|
|
||||||
|
|
||||||
|
|
||||||
.. _`interdependent resources`:
|
|
||||||
|
|
||||||
Interdepdendent resources
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
You can not only use funcargs in test functions but also in their factories
|
|
||||||
themselves. Extending the previous example we can instantiate another
|
|
||||||
object ``app`` and stick the ``smtp`` resource into it like this::
|
|
||||||
|
|
||||||
# content of test_appsetup.py
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
@pytest.factory(scope="module")
|
|
||||||
class app:
|
|
||||||
def __init__(self, smtp):
|
|
||||||
self.smtp = smtp
|
|
||||||
|
|
||||||
def test_exists(app):
|
|
||||||
assert app.smtp
|
|
||||||
|
|
||||||
Here we declare an ``app`` class to be a factory and have its init-method
|
|
||||||
use the previously defined ``smtp`` resource. Let's run it::
|
|
||||||
|
|
||||||
$ py.test -v test_appsetup.py
|
|
||||||
=========================== test session starts ============================
|
|
||||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
|
|
||||||
cachedir: /home/hpk/tmp/doc-exec-423/.cache
|
|
||||||
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
|
||||||
collecting ... collected 2 items
|
|
||||||
|
|
||||||
test_appsetup.py:12: test_exists[merlinux.eu] PASSED
|
|
||||||
test_appsetup.py:12: test_exists[mail.python.org] PASSED
|
|
||||||
|
|
||||||
========================= 2 passed in 6.82 seconds =========================
|
|
||||||
|
|
||||||
Due to the parametrization of ``smtp`` the test will run twice with two
|
|
||||||
different ``App`` instances and respective smtp servers. There is no
|
|
||||||
need for the ``app`` factory to be aware of the parametrization. Note
|
|
||||||
that the ``app`` factory has a scope of ``module`` but uses the
|
|
||||||
session-scoped ``smtp`` object: it is fine for factories to use
|
|
||||||
"broader" scoped resources but not the other way round. A
|
|
||||||
session-scoped resource could not use a module-scoped resource in a
|
|
||||||
meaningful way.
|
|
||||||
|
|
||||||
.. _`automatic per-resource grouping`:
|
|
||||||
|
|
||||||
Grouping tests by resource parameters
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
.. regendoc: wipe
|
|
||||||
|
|
||||||
pytest minimizes the number of active resources during test runs.
|
|
||||||
If you have a parametrized resource, then all the tests using one
|
|
||||||
resource instance will execute one after another. Then any finalizers
|
|
||||||
are called for that resource instance and then the next parametrized
|
|
||||||
resource instance is created and its tests are run. Among other things,
|
|
||||||
this eases testing of applications which create and use global state.
|
|
||||||
|
|
||||||
The following example uses two parametrized funcargs, one of which is
|
|
||||||
scoped on a per-module basis, and all the functions perform ``print`` call s
|
|
||||||
to show the flow of calls::
|
|
||||||
|
|
||||||
# content of test_module.py
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
@pytest.factory(scope="module", params=["mod1", "mod2"])
|
|
||||||
def modarg(request):
|
|
||||||
param = request.param
|
|
||||||
print "create", param
|
|
||||||
def fin():
|
|
||||||
print "fin", param
|
|
||||||
request.addfinalizer(fin)
|
|
||||||
return param
|
|
||||||
|
|
||||||
@pytest.factory(scope="function", params=[1,2])
|
|
||||||
def otherarg(request):
|
|
||||||
return request.param
|
|
||||||
|
|
||||||
def test_0(otherarg):
|
|
||||||
print " test0", otherarg
|
|
||||||
def test_1(modarg):
|
|
||||||
print " test1", modarg
|
|
||||||
def test_2(otherarg, modarg):
|
|
||||||
print " test2", otherarg, modarg
|
|
||||||
|
|
||||||
Let's run the tests in verbose mode and with looking at the print-output::
|
|
||||||
|
|
||||||
$ py.test -v -s test_module.py
|
|
||||||
=========================== test session starts ============================
|
|
||||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
|
|
||||||
cachedir: /home/hpk/tmp/doc-exec-423/.cache
|
|
||||||
plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
|
|
||||||
collecting ... collected 8 items
|
|
||||||
|
|
||||||
test_module.py:16: test_0[1] PASSED
|
|
||||||
test_module.py:16: test_0[2] PASSED
|
|
||||||
test_module.py:18: test_1[mod1] PASSED
|
|
||||||
test_module.py:20: test_2[1-mod1] PASSED
|
|
||||||
test_module.py:20: test_2[2-mod1] PASSED
|
|
||||||
test_module.py:18: test_1[mod2] PASSED
|
|
||||||
test_module.py:20: test_2[1-mod2] PASSED
|
|
||||||
test_module.py:20: test_2[2-mod2] PASSED
|
|
||||||
|
|
||||||
========================= 8 passed in 0.02 seconds =========================
|
|
||||||
test0 1
|
|
||||||
test0 2
|
|
||||||
create mod1
|
|
||||||
test1 mod1
|
|
||||||
test2 1 mod1
|
|
||||||
test2 2 mod1
|
|
||||||
fin mod1
|
|
||||||
create mod2
|
|
||||||
test1 mod2
|
|
||||||
test2 1 mod2
|
|
||||||
test2 2 mod2
|
|
||||||
fin mod2
|
|
||||||
|
|
||||||
You can see that the parametrized module-scoped ``modarg`` resource caused
|
|
||||||
an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed
|
|
||||||
before the ``mod2`` resource was setup.
|
|
||||||
|
|
||||||
.. currentmodule:: _pytest.python
|
|
||||||
.. _`request`:
|
|
||||||
|
|
||||||
``request``: interacting with test invocation context
|
|
||||||
-------------------------------------------------------
|
|
||||||
|
|
||||||
The ``request`` object may be received by `@pytest.factory`_ or
|
|
||||||
:ref:`@pytest.setup <setup>` marked functions and provides methods
|
|
||||||
|
|
||||||
* to inspect attributes of the requesting test context, such as
|
|
||||||
``function``, ``cls``, ``module``, ``session`` and the pytest
|
|
||||||
``config`` object. A request object passed to a parametrized factory
|
|
||||||
will also carry a ``request.param`` object (A parametrized factory and
|
|
||||||
all of its dependent tests will be called with each of the factory-specified
|
|
||||||
``params``).
|
|
||||||
|
|
||||||
* to add finalizers/teardowns to be invoked when the last
|
|
||||||
test of the requesting test context executes
|
|
||||||
|
|
||||||
.. autoclass:: _pytest.python.FuncargRequest()
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
.. _`test generators`:
|
.. _`test generators`:
|
||||||
|
|
214
doc/en/setup.txt
214
doc/en/setup.txt
|
@ -1,212 +1,12 @@
|
||||||
.. _xunitsetup:
|
|
||||||
.. _setup:
|
|
||||||
.. _`setup functions`:
|
|
||||||
.. _`@pytest.setup`:
|
|
||||||
|
|
||||||
``@setup`` functions or: xunit on steroids
|
Page has moved to fixture
|
||||||
========================================================
|
========================================================
|
||||||
|
|
||||||
.. versionadded:: 2.3
|
During development prior to the pytest-2.3 release the name
|
||||||
|
``pytest.setup`` was used but before the release it was renamed
|
||||||
.. _`funcargs`: funcargs.html
|
to :ref:`pytest.fixture` mainly to avoid the misconception that there
|
||||||
.. _`test parametrization`: funcargs.html#parametrizing-tests
|
should be a ``pytest.teardown`` as well.
|
||||||
.. _`unittest plugin`: plugin/unittest.html
|
|
||||||
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
|
||||||
|
|
||||||
Python, Java and many other languages support a so called xUnit_ style
|
|
||||||
of resource setup. This typically involves the call of a ``setup``
|
|
||||||
("fixture") method before running a test function and ``teardown`` after
|
|
||||||
it has finished. Unlike :ref:`injected resources <resources>` setup
|
|
||||||
functions work indirectly by causing global side effects or
|
|
||||||
setting test case attributes which test methods can then access.
|
|
||||||
|
|
||||||
pytest originally introduced in 2005 a scope-specific model of detecting
|
|
||||||
setup and teardown functions on a per-module, class or function basis.
|
|
||||||
The Python unittest package and nose have subsequently incorporated them.
|
|
||||||
This model remains supported by pytest as :ref:`old-style xunit`.
|
|
||||||
|
|
||||||
Moreover, pytest-2.3 introduces a new ``pytest.setup()`` decorator
|
|
||||||
to mark functions as setup functions which allow to implement everything
|
|
||||||
you can do with the old-style and much more. Specifically setup functions:
|
|
||||||
|
|
||||||
- can receive :ref:`resources through funcargs <resources>`,
|
|
||||||
- fully interoperate with parametrized resources,
|
|
||||||
- can be defined in a plugin or :ref:`conftest.py <conftest.py>` file and get called
|
|
||||||
on a per-session, per-module, per-class or per-function basis,
|
|
||||||
- can access the :ref:`request <request>` for which the setup is called,
|
|
||||||
- can precisely control teardown by registering one or multiple
|
|
||||||
teardown functions as soon as they have performed some actions
|
|
||||||
which need undoing, eliminating the no need for a separate
|
|
||||||
"teardown" decorator.
|
|
||||||
- allow to separate different setup concerns even if they
|
|
||||||
happen to work in the same scope
|
|
||||||
|
|
||||||
All of these features are now demonstrated by little examples.
|
|
||||||
|
|
||||||
.. _`new_setup`:
|
|
||||||
.. _`@pytest.setup`:
|
|
||||||
|
|
||||||
basic per-function setup
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
.. regendoc:wipe
|
|
||||||
|
|
||||||
Suppose you want to have a clean directory with a single
|
|
||||||
file entry for each test function in a module and have
|
|
||||||
the test execute with this directory as current working dir::
|
|
||||||
|
|
||||||
# content of test_funcdir.py
|
|
||||||
import pytest
|
|
||||||
import os
|
|
||||||
|
|
||||||
@pytest.setup()
|
|
||||||
def mydir(tmpdir):
|
|
||||||
tmpdir.join("myfile").write("example content")
|
|
||||||
old = tmpdir.chdir()
|
|
||||||
|
|
||||||
def test_function1():
|
|
||||||
assert os.path.exists("myfile")
|
|
||||||
f = open("anotherfile", "w")
|
|
||||||
f.write("")
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def test_function2():
|
|
||||||
assert os.path.exists("myfile")
|
|
||||||
assert not os.path.exists("anotherfile")
|
|
||||||
|
|
||||||
Our ``mydir`` setup function is executed on a per-function basis,
|
|
||||||
the default scope used by the ``pytest.setup`` decorator.
|
|
||||||
It accesses the ``tmpdir`` resource which provides a new empty
|
|
||||||
directory path object. The ``test_function2`` here checks that
|
|
||||||
it executes with a fresh directory and that it
|
|
||||||
does not see the previously created ``anotherfile``. We can
|
|
||||||
thus expect two passing tests::
|
|
||||||
|
|
||||||
$ py.test -v
|
|
||||||
=========================== test session starts ============================
|
|
||||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev7 -- /home/hpk/venv/1/bin/python
|
|
||||||
cachedir: /home/hpk/tmp/doc-exec-410/.cache
|
|
||||||
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
|
|
||||||
collecting ... collected 2 items
|
|
||||||
|
|
||||||
test_funcdir.py:9: test_function1 PASSED
|
|
||||||
test_funcdir.py:15: test_function2 PASSED
|
|
||||||
|
|
||||||
========================= 2 passed in 0.26 seconds =========================
|
|
||||||
|
|
||||||
per-function setup, for every function of a project
|
|
||||||
------------------------------------------------------------
|
|
||||||
|
|
||||||
If you want to define a setup per-function but want to apply
|
|
||||||
it to every function in your project you don't need to duplicate
|
|
||||||
the setup-definition into each test module. Instead you can put
|
|
||||||
it into a ``conftest.py`` file into the root of your project::
|
|
||||||
|
|
||||||
# content of conftest.py
|
|
||||||
import pytest
|
|
||||||
import os
|
|
||||||
|
|
||||||
@pytest.setup()
|
|
||||||
def cleandir(tmpdir):
|
|
||||||
old = tmpdir.chdir()
|
|
||||||
|
|
||||||
The ``cleandir`` setup function will be called for every test function
|
|
||||||
below the directory tree where ``conftest.py`` resides. In this
|
|
||||||
case it just uses the builtin ``tmpdir`` resource to change to the
|
|
||||||
empty directory ahead of running a test.
|
|
||||||
|
|
||||||
test modules accessing a global resource
|
|
||||||
-------------------------------------------------------
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Relying on `global state is considered bad programming practise <http://en.wikipedia.org/wiki/Global_variable>`_ but when you work with an application
|
|
||||||
that relies on it you often have no choice.
|
|
||||||
|
|
||||||
If you want test modules to access a global resource,
|
|
||||||
you can stick the resource to the module globals in
|
|
||||||
a per-module setup function. We use a :ref:`resource factory
|
|
||||||
<@pytest.factory>` to create our global resource::
|
|
||||||
|
|
||||||
# content of conftest.py
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
class GlobalResource:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pytest.factory(scope="session")
|
|
||||||
def globresource():
|
|
||||||
return GlobalResource()
|
|
||||||
|
|
||||||
@pytest.setup(scope="module")
|
|
||||||
def setresource(request, globresource):
|
|
||||||
request.module.globresource = globresource
|
|
||||||
|
|
||||||
Now any test module can access ``globresource`` as a module global::
|
|
||||||
|
|
||||||
# content of test_glob.py
|
|
||||||
|
|
||||||
def test_1():
|
|
||||||
print ("test_1 %s" % globresource)
|
|
||||||
def test_2():
|
|
||||||
print ("test_2 %s" % globresource)
|
|
||||||
|
|
||||||
Let's run this module without output-capturing::
|
|
||||||
|
|
||||||
$ py.test -qs test_glob.py
|
|
||||||
collecting ... collected 2 items
|
|
||||||
..
|
|
||||||
2 passed in 0.02 seconds
|
|
||||||
test_1 <conftest.GlobalResource instance at 0x13197e8>
|
|
||||||
test_2 <conftest.GlobalResource instance at 0x13197e8>
|
|
||||||
|
|
||||||
The two tests see the same global ``globresource`` object.
|
|
||||||
|
|
||||||
Parametrizing the global resource
|
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
|
|
||||||
We extend the previous example and add parametrization to the globresource
|
|
||||||
factory and also add a finalizer::
|
|
||||||
|
|
||||||
# content of conftest.py
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
class GlobalResource:
|
|
||||||
def __init__(self, param):
|
|
||||||
self.param = param
|
|
||||||
|
|
||||||
@pytest.factory(scope="session", params=[1,2])
|
|
||||||
def globresource(request):
|
|
||||||
g = GlobalResource(request.param)
|
|
||||||
def fin():
|
|
||||||
print "finalizing", g
|
|
||||||
request.addfinalizer(fin)
|
|
||||||
return g
|
|
||||||
|
|
||||||
@pytest.setup(scope="module")
|
|
||||||
def setresource(request, globresource):
|
|
||||||
request.module.globresource = globresource
|
|
||||||
|
|
||||||
And then re-run our test module::
|
|
||||||
|
|
||||||
$ py.test -qs test_glob.py
|
|
||||||
collecting ... collected 4 items
|
|
||||||
....
|
|
||||||
4 passed in 0.02 seconds
|
|
||||||
test_1 <conftest.GlobalResource instance at 0x1922e18>
|
|
||||||
test_2 <conftest.GlobalResource instance at 0x1922e18>
|
|
||||||
finalizing <conftest.GlobalResource instance at 0x1922e18>
|
|
||||||
test_1 <conftest.GlobalResource instance at 0x1925518>
|
|
||||||
test_2 <conftest.GlobalResource instance at 0x1925518>
|
|
||||||
finalizing <conftest.GlobalResource instance at 0x1925518>
|
|
||||||
|
|
||||||
We are now running the two tests twice with two different global resource
|
|
||||||
instances. Note that the tests are ordered such that only
|
|
||||||
one instance is active at any given time: the finalizer of
|
|
||||||
the first globresource instance is called before the second
|
|
||||||
instance is created and sent to the setup functions.
|
|
||||||
|
|
||||||
|
|
||||||
|
Please refer to :ref:`pytest.fixture` for information on the new
|
||||||
|
fixture functions.
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
|
|
||||||
.. _`old-style xunit`:
|
.. _`classic xunit`:
|
||||||
|
|
||||||
Old-style xunit-style setup
|
classic xunit-style setup
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This section describes the old way how you can implement setup and
|
This section describes the old way how you can implement setup and
|
||||||
teardown on a per-module/class/function basis. It remains fully
|
teardown on a per-module/class/function basis. It remains fully
|
||||||
supported but it is recommended to rather use :ref:`@setup functions
|
supported but it is recommended to rather use :ref:`fixture functions
|
||||||
<setup>` or :ref:`injected resources <resources>` for implementing your
|
<fixture>` or :ref:`funcargs <resources>` for implementing your
|
||||||
setup needs.
|
needs to prepare and fix the test state for your tests.
|
||||||
|
|
||||||
Module level setup/teardown
|
Module level setup/teardown
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue