Skip to content

Commit 0392f2c

Browse files
committed
[docs] Reword section about asyncio event loops in "concepts".
The section now explains the concept of "scoped loops". Signed-off-by: Michael Seifert <[email protected]>
1 parent 5fc341a commit 0392f2c

File tree

3 files changed

+62
-13
lines changed

3 files changed

+62
-13
lines changed

docs/source/concepts.rst

+37-13
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,43 @@ Concepts
44

55
asyncio event loops
66
===================
7-
pytest-asyncio runs each test item in its own asyncio event loop. The loop can be accessed via ``asyncio.get_running_loop()``.
8-
9-
.. code-block:: python
10-
11-
async def test_runs_in_a_loop():
12-
assert asyncio.get_running_loop()
13-
14-
Synchronous test functions can get access to an asyncio event loop via the `event_loop` fixture.
15-
16-
.. code-block:: python
17-
18-
def test_can_access_current_loop(event_loop):
19-
assert event_loop
7+
In order to understand how pytest-asyncio works, it helps to understand how pytest collectors work.
8+
If you already know about pytest collectors, please :ref:`skip ahead <pytest-asyncio-event-loops>`.
9+
Otherwise, continue reading.
10+
Let's assume we have a test suite with a file named *test_all_the_things.py* holding a single test, async or not:
11+
12+
.. include:: concepts_function_scope_example.py
13+
:code: python
14+
15+
The file *test_all_the_things.py* is a Python module with a Python test function.
16+
When we run pytest, the test runner descends into Python packages, modules, and classes, in order to find all tests, regardless whether the tests will run or not.
17+
This process is referred to as *test collection* by pytest.
18+
In our particular example, pytest will find our test module and the test function.
19+
We can visualize the collection result by running ``pytest --collect-only``::
20+
21+
<Module test_all_the_things.py>
22+
<Function test_runs_in_a_loop>
23+
24+
The example illustrates that the code of our test suite is hierarchical.
25+
Pytest uses so called *collectors* for each level of the hierarchy.
26+
Our contrived example test suite uses the *Module* and *Function* collectors, but real world test code may contain additional hierarchy levels via the *Package* or *Class* collectors.
27+
There's also a special *Session* collector at the root of the hierarchy.
28+
You may notice that the individual levels resemble the possible `scopes of a pytest fixture. <https://docs.pytest.org/en/7.4.x/how-to/fixtures.html#scope-sharing-fixtures-across-classes-modules-packages-or-session>`__
29+
30+
.. _pytest-asyncio-event-loops:
31+
32+
Pytest-asyncio provides one asyncio event loop for each pytest collector.
33+
By default, each test runs in the event loop provided by the *Function* collector, i.e. tests use the loop with the narrowest scope.
34+
This gives the highest level of isolation between tests.
35+
If two or more tests share a common ancestor collector, the tests can be configured to run in their ancestor's loop by passing the appropriate *scope* keyword argument to the *asyncio* mark.
36+
For example, the following two tests use the asyncio event loop provided by the *Module* collector:
37+
38+
.. include:: concepts_module_scope_example.py
39+
:code: python
40+
41+
It's highly recommended for neighboring tests to use the same event loop scope.
42+
For example, all tests in a class or module should use the same scope.
43+
Assigning neighboring tests to different event loop scopes is discouraged as it can make test code hard to follow.
2044

2145
Test discovery modes
2246
====================
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import asyncio
2+
3+
import pytest
4+
5+
6+
@pytest.mark.asyncio
7+
async def test_runs_in_a_loop():
8+
assert asyncio.get_running_loop()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import asyncio
2+
3+
import pytest
4+
5+
loop: asyncio.AbstractEventLoop
6+
7+
8+
@pytest.mark.asyncio(scope="module")
9+
async def test_remember_loop():
10+
global loop
11+
loop = asyncio.get_running_loop()
12+
13+
14+
@pytest.mark.asyncio(scope="module")
15+
async def test_runs_in_a_loop():
16+
global loop
17+
assert asyncio.get_running_loop() is loop

0 commit comments

Comments
 (0)