205 lines
6.9 KiB
Plaintext
205 lines
6.9 KiB
Plaintext
|
.. _xunitsetup:
|
||
|
.. _setup:
|
||
|
|
||
|
``@setup`` functions or: xunit on steroids
|
||
|
========================================================
|
||
|
|
||
|
.. versionadded:: 2.3
|
||
|
|
||
|
.. _`funcargs`: funcargs.html
|
||
|
.. _`test parametrization`: funcargs.html#parametrizing-tests
|
||
|
.. _`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 fine-grained model of detecting
|
||
|
setup and teardown functions on a per-module, class or function basis.
|
||
|
The Python unittest module and nose have subsequently incorporated them.
|
||
|
This model remains supported as :ref:`old-style xunit`.
|
||
|
|
||
|
With pytest-2.3 a new ``pytest.setup()`` decorator is introduced
|
||
|
to mark functions as setup functions which:
|
||
|
|
||
|
- can receive resources through funcargs,
|
||
|
- fully interoperate with parametrized resources,
|
||
|
- can be defined in a plugin or conftest.py file and get called
|
||
|
on a per-session, per-module, per-class or per-function basis,
|
||
|
- can access the full :ref:`testcontext` 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.mark.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.mark.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.mark.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 specifically
|
||
|
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.mark.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
|
||
|
-------------------------------------------------------
|
||
|
|
||
|
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.mark.factory>` to create our global resource::
|
||
|
|
||
|
# content of conftest.py
|
||
|
import pytest
|
||
|
|
||
|
class GlobalResource:
|
||
|
def __init__(self):
|
||
|
pass
|
||
|
|
||
|
@pytest.mark.factory(scope="session")
|
||
|
def globresource():
|
||
|
return GlobalResource()
|
||
|
|
||
|
@pytest.mark.setup(scope="module")
|
||
|
def setresource(testcontext, globresource):
|
||
|
testcontext.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.mark.factory(scope="session", params=[1,2])
|
||
|
def globresource(testcontext):
|
||
|
g = GlobalResource(testcontext.param)
|
||
|
def fin():
|
||
|
print "finalizing", g
|
||
|
testcontext.addfinalizer(fin)
|
||
|
return g
|
||
|
|
||
|
@pytest.mark.setup(scope="module")
|
||
|
def setresource(testcontext, globresource):
|
||
|
testcontext.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.
|
||
|
|
||
|
|
||
|
|