Merge pull request #8312 from matthewhughes934/add-regendoc-runs-for-fixture-docs
This commit is contained in:
commit
bebb6953eb
|
@ -375,7 +375,7 @@ Fixtures are reusable
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
One of the things that makes pytest's fixture system so powerful, is that it
|
One of the things that makes pytest's fixture system so powerful, is that it
|
||||||
gives us the abilty to define a generic setup step that can reused over and
|
gives us the ability to define a generic setup step that can reused over and
|
||||||
over, just like a normal function would be used. Two different tests can request
|
over, just like a normal function would be used. Two different tests can request
|
||||||
the same fixture and have pytest give each test their own result from that
|
the same fixture and have pytest give each test their own result from that
|
||||||
fixture.
|
fixture.
|
||||||
|
@ -829,6 +829,8 @@ This system can be leveraged in two ways.
|
||||||
1. ``yield`` fixtures (recommended)
|
1. ``yield`` fixtures (recommended)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. regendoc: wipe
|
||||||
|
|
||||||
"Yield" fixtures ``yield`` instead of ``return``. With these
|
"Yield" fixtures ``yield`` instead of ``return``. With these
|
||||||
fixtures, we can run some code and pass an object back to the requesting
|
fixtures, we can run some code and pass an object back to the requesting
|
||||||
fixture/test, just like with the other fixtures. The only differences are:
|
fixture/test, just like with the other fixtures. The only differences are:
|
||||||
|
@ -844,17 +846,48 @@ Once the test is finished, pytest will go back down the list of fixtures, but in
|
||||||
the *reverse order*, taking each one that yielded, and running the code inside
|
the *reverse order*, taking each one that yielded, and running the code inside
|
||||||
it that was *after* the ``yield`` statement.
|
it that was *after* the ``yield`` statement.
|
||||||
|
|
||||||
As a simple example, let's say we want to test sending email from one user to
|
As a simple example, consider this basic email module:
|
||||||
another. We'll have to first make each user, then send the email from one user
|
|
||||||
to the other, and finally assert that the other user received that message in
|
.. code-block:: python
|
||||||
their inbox. If we want to clean up after the test runs, we'll likely have to
|
|
||||||
make sure the other user's mailbox is emptied before deleting that user,
|
# content of emaillib.py
|
||||||
otherwise the system may complain.
|
class MailAdminClient:
|
||||||
|
def create_user(self):
|
||||||
|
return MailUser()
|
||||||
|
|
||||||
|
def delete_user(self, user):
|
||||||
|
# do some cleanup
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MailUser:
|
||||||
|
def __init__(self):
|
||||||
|
self.inbox = []
|
||||||
|
|
||||||
|
def send_email(self, email, other):
|
||||||
|
other.inbox.append(email)
|
||||||
|
|
||||||
|
def clear_mailbox(self):
|
||||||
|
self.inbox.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class Email:
|
||||||
|
def __init__(self, subject, body):
|
||||||
|
self.subject = subject
|
||||||
|
self.body = body
|
||||||
|
|
||||||
|
Let's say we want to test sending email from one user to another. We'll have to
|
||||||
|
first make each user, then send the email from one user to the other, and
|
||||||
|
finally assert that the other user received that message in their inbox. If we
|
||||||
|
want to clean up after the test runs, we'll likely have to make sure the other
|
||||||
|
user's mailbox is emptied before deleting that user, otherwise the system may
|
||||||
|
complain.
|
||||||
|
|
||||||
Here's what that might look like:
|
Here's what that might look like:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of test_emaillib.py
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from emaillib import Email, MailAdminClient
|
from emaillib import Email, MailAdminClient
|
||||||
|
@ -869,17 +902,17 @@ Here's what that might look like:
|
||||||
def sending_user(mail_admin):
|
def sending_user(mail_admin):
|
||||||
user = mail_admin.create_user()
|
user = mail_admin.create_user()
|
||||||
yield user
|
yield user
|
||||||
admin_client.delete_user(user)
|
mail_admin.delete_user(user)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def receiving_user(mail_admin):
|
def receiving_user(mail_admin):
|
||||||
user = mail_admin.create_user()
|
user = mail_admin.create_user()
|
||||||
yield user
|
yield user
|
||||||
admin_client.delete_user(user)
|
mail_admin.delete_user(user)
|
||||||
|
|
||||||
|
|
||||||
def test_email_received(sending_user, receiving_user, email):
|
def test_email_received(sending_user, receiving_user):
|
||||||
email = Email(subject="Hey!", body="How's it going?")
|
email = Email(subject="Hey!", body="How's it going?")
|
||||||
sending_user.send_email(email, receiving_user)
|
sending_user.send_email(email, receiving_user)
|
||||||
assert email in receiving_user.inbox
|
assert email in receiving_user.inbox
|
||||||
|
@ -891,6 +924,12 @@ There is a risk that even having the order right on the teardown side of things
|
||||||
doesn't guarantee a safe cleanup. That's covered in a bit more detail in
|
doesn't guarantee a safe cleanup. That's covered in a bit more detail in
|
||||||
:ref:`safe teardowns`.
|
:ref:`safe teardowns`.
|
||||||
|
|
||||||
|
.. code-block:: pytest
|
||||||
|
|
||||||
|
$ pytest -q test_emaillib.py
|
||||||
|
. [100%]
|
||||||
|
1 passed in 0.12s
|
||||||
|
|
||||||
Handling errors for yield fixture
|
Handling errors for yield fixture
|
||||||
"""""""""""""""""""""""""""""""""
|
"""""""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
@ -902,7 +941,7 @@ attempt to tear them down as it normally would.
|
||||||
2. Adding finalizers directly
|
2. Adding finalizers directly
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
While yield fixtures are considered to be the cleaner and more straighforward
|
While yield fixtures are considered to be the cleaner and more straightforward
|
||||||
option, there is another choice, and that is to add "finalizer" functions
|
option, there is another choice, and that is to add "finalizer" functions
|
||||||
directly to the test's `request-context`_ object. It brings a similar result as
|
directly to the test's `request-context`_ object. It brings a similar result as
|
||||||
yield fixtures, but requires a bit more verbosity.
|
yield fixtures, but requires a bit more verbosity.
|
||||||
|
@ -922,6 +961,7 @@ Here's how the previous example would look using the ``addfinalizer`` method:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of test_emaillib.py
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from emaillib import Email, MailAdminClient
|
from emaillib import Email, MailAdminClient
|
||||||
|
@ -936,7 +976,7 @@ Here's how the previous example would look using the ``addfinalizer`` method:
|
||||||
def sending_user(mail_admin):
|
def sending_user(mail_admin):
|
||||||
user = mail_admin.create_user()
|
user = mail_admin.create_user()
|
||||||
yield user
|
yield user
|
||||||
admin_client.delete_user(user)
|
mail_admin.delete_user(user)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -944,7 +984,7 @@ Here's how the previous example would look using the ``addfinalizer`` method:
|
||||||
user = mail_admin.create_user()
|
user = mail_admin.create_user()
|
||||||
|
|
||||||
def delete_user():
|
def delete_user():
|
||||||
admin_client.delete_user(user)
|
mail_admin.delete_user(user)
|
||||||
|
|
||||||
request.addfinalizer(delete_user)
|
request.addfinalizer(delete_user)
|
||||||
return user
|
return user
|
||||||
|
@ -956,7 +996,7 @@ Here's how the previous example would look using the ``addfinalizer`` method:
|
||||||
sending_user.send_email(_email, receiving_user)
|
sending_user.send_email(_email, receiving_user)
|
||||||
|
|
||||||
def empty_mailbox():
|
def empty_mailbox():
|
||||||
receiving_user.delete_email(_email)
|
receiving_user.clear_mailbox()
|
||||||
|
|
||||||
request.addfinalizer(empty_mailbox)
|
request.addfinalizer(empty_mailbox)
|
||||||
return _email
|
return _email
|
||||||
|
@ -969,6 +1009,12 @@ Here's how the previous example would look using the ``addfinalizer`` method:
|
||||||
It's a bit longer than yield fixtures and a bit more complex, but it
|
It's a bit longer than yield fixtures and a bit more complex, but it
|
||||||
does offer some nuances for when you're in a pinch.
|
does offer some nuances for when you're in a pinch.
|
||||||
|
|
||||||
|
.. code-block:: pytest
|
||||||
|
|
||||||
|
$ pytest -q test_emaillib.py
|
||||||
|
. [100%]
|
||||||
|
1 passed in 0.12s
|
||||||
|
|
||||||
.. _`safe teardowns`:
|
.. _`safe teardowns`:
|
||||||
|
|
||||||
Safe teardowns
|
Safe teardowns
|
||||||
|
@ -984,6 +1030,7 @@ above):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of test_emaillib.py
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from emaillib import Email, MailAdminClient
|
from emaillib import Email, MailAdminClient
|
||||||
|
@ -995,11 +1042,11 @@ above):
|
||||||
sending_user = mail_admin.create_user()
|
sending_user = mail_admin.create_user()
|
||||||
receiving_user = mail_admin.create_user()
|
receiving_user = mail_admin.create_user()
|
||||||
email = Email(subject="Hey!", body="How's it going?")
|
email = Email(subject="Hey!", body="How's it going?")
|
||||||
sending_user.send_emai(email, receiving_user)
|
sending_user.send_email(email, receiving_user)
|
||||||
yield receiving_user, email
|
yield receiving_user, email
|
||||||
receiving_user.delete_email(email)
|
receiving_user.clear_mailbox()
|
||||||
admin_client.delete_user(sending_user)
|
mail_admin.delete_user(sending_user)
|
||||||
admin_client.delete_user(receiving_user)
|
mail_admin.delete_user(receiving_user)
|
||||||
|
|
||||||
|
|
||||||
def test_email_received(setup):
|
def test_email_received(setup):
|
||||||
|
@ -1016,6 +1063,12 @@ One option might be to go with the ``addfinalizer`` method instead of yield
|
||||||
fixtures, but that might get pretty complex and difficult to maintain (and it
|
fixtures, but that might get pretty complex and difficult to maintain (and it
|
||||||
wouldn't be compact anymore).
|
wouldn't be compact anymore).
|
||||||
|
|
||||||
|
.. code-block:: pytest
|
||||||
|
|
||||||
|
$ pytest -q test_emaillib.py
|
||||||
|
. [100%]
|
||||||
|
1 passed in 0.12s
|
||||||
|
|
||||||
.. _`safe fixture structure`:
|
.. _`safe fixture structure`:
|
||||||
|
|
||||||
Safe fixture structure
|
Safe fixture structure
|
||||||
|
@ -1026,7 +1079,7 @@ making one state-changing action each, and then bundling them together with
|
||||||
their teardown code, as :ref:`the email examples above <yield fixtures>` showed.
|
their teardown code, as :ref:`the email examples above <yield fixtures>` showed.
|
||||||
|
|
||||||
The chance that a state-changing operation can fail but still modify state is
|
The chance that a state-changing operation can fail but still modify state is
|
||||||
neglibible, as most of these operations tend to be `transaction`_-based (at
|
negligible, as most of these operations tend to be `transaction`_-based (at
|
||||||
least at the level of testing where state could be left behind). So if we make
|
least at the level of testing where state could be left behind). So if we make
|
||||||
sure that any successful state-changing action gets torn down by moving it to a
|
sure that any successful state-changing action gets torn down by moving it to a
|
||||||
separate fixture function and separating it from other, potentially failing
|
separate fixture function and separating it from other, potentially failing
|
||||||
|
@ -1124,7 +1177,7 @@ never have been made.
|
||||||
.. _`conftest.py`:
|
.. _`conftest.py`:
|
||||||
.. _`conftest`:
|
.. _`conftest`:
|
||||||
|
|
||||||
Fixture availabiility
|
Fixture availability
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Fixture availability is determined from the perspective of the test. A fixture
|
Fixture availability is determined from the perspective of the test. A fixture
|
||||||
|
@ -1410,9 +1463,9 @@ pytest doesn't know where ``c`` should go in the case, so it should be assumed
|
||||||
that it could go anywhere between ``g`` and ``b``.
|
that it could go anywhere between ``g`` and ``b``.
|
||||||
|
|
||||||
This isn't necessarily bad, but it's something to keep in mind. If the order
|
This isn't necessarily bad, but it's something to keep in mind. If the order
|
||||||
they execute in could affect the behavior a test is targetting, or could
|
they execute in could affect the behavior a test is targeting, or could
|
||||||
otherwise influence the result of a test, then the order should be defined
|
otherwise influence the result of a test, then the order should be defined
|
||||||
explicitely in a way that allows pytest to linearize/"flatten" that order.
|
explicitly in a way that allows pytest to linearize/"flatten" that order.
|
||||||
|
|
||||||
.. _`autouse order`:
|
.. _`autouse order`:
|
||||||
|
|
||||||
|
@ -1506,7 +1559,7 @@ of what we've gone over so far.
|
||||||
|
|
||||||
All that's needed is stepping up to a larger scope, then having the **act**
|
All that's needed is stepping up to a larger scope, then having the **act**
|
||||||
step defined as an autouse fixture, and finally, making sure all the fixtures
|
step defined as an autouse fixture, and finally, making sure all the fixtures
|
||||||
are targetting that highler level scope.
|
are targeting that higher level scope.
|
||||||
|
|
||||||
Let's pull :ref:`an example from above <safe fixture structure>`, and tweak it a
|
Let's pull :ref:`an example from above <safe fixture structure>`, and tweak it a
|
||||||
bit. Let's say that in addition to checking for a welcome message in the header,
|
bit. Let's say that in addition to checking for a welcome message in the header,
|
||||||
|
@ -1646,7 +1699,7 @@ again, nothing much has changed:
|
||||||
|
|
||||||
.. code-block:: pytest
|
.. code-block:: pytest
|
||||||
|
|
||||||
$ pytest -s -q --tb=no
|
$ pytest -s -q --tb=no test_module.py
|
||||||
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
|
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
|
||||||
|
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
|
@ -1931,11 +1984,13 @@ Running the above tests results in the following test IDs being used:
|
||||||
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
|
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
|
||||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||||
rootdir: $REGENDOC_TMPDIR
|
rootdir: $REGENDOC_TMPDIR
|
||||||
collected 10 items
|
collected 11 items
|
||||||
|
|
||||||
<Module test_anothersmtp.py>
|
<Module test_anothersmtp.py>
|
||||||
<Function test_showhelo[smtp.gmail.com]>
|
<Function test_showhelo[smtp.gmail.com]>
|
||||||
<Function test_showhelo[mail.python.org]>
|
<Function test_showhelo[mail.python.org]>
|
||||||
|
<Module test_emaillib.py>
|
||||||
|
<Function test_email_received>
|
||||||
<Module test_ids.py>
|
<Module test_ids.py>
|
||||||
<Function test_a[spam]>
|
<Function test_a[spam]>
|
||||||
<Function test_a[ham]>
|
<Function test_a[ham]>
|
||||||
|
@ -1947,7 +2002,7 @@ Running the above tests results in the following test IDs being used:
|
||||||
<Function test_ehlo[mail.python.org]>
|
<Function test_ehlo[mail.python.org]>
|
||||||
<Function test_noop[mail.python.org]>
|
<Function test_noop[mail.python.org]>
|
||||||
|
|
||||||
======================= 10 tests collected in 0.12s ========================
|
======================= 11 tests collected in 0.12s ========================
|
||||||
|
|
||||||
.. _`fixture-parametrize-marks`:
|
.. _`fixture-parametrize-marks`:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue