In pytest fixtures nuts and bolts, I noted that you can specify session scope so that a fixture will only run once per test session and be available across multiple test functions, classes, and modules.

In this post, I’m going to show a simple example so you can see it in action.

Here’s the table from the previous post:

<td align="left">
  Run once per test
</td>
<td align="left">
  Run once per class of tests
</td>
<td align="left">
  Run once per module
</td>
<td align="left">
  Run once per session
</td>
function
class
module
session

A separate file for fixtures, conftest.py

With function, class, and module scope, it is completely reasonable for the fixture code to be in the same file as the tests.
But now suddenly, with session, that doesn’t make sense anymore.

We can put them in conftest.py. This is a special named file that pytest looks for.
The documentation says that it’s for local plugins, but we can use it for local fixtures as well. See the pytest.org site for placement and scope of conftest.py.

Simple example of session scope fixtures

I think it’s clearest to just see this in action.

I’ve got 4 files:

  • conftest.py
    • 2 fixtures
    • my_own_session_run_at_beginning, an autouse fixture with session scope
    • some_resource, a normal non-autouse fixture with session scope
  • test_alpha.py
    • 2 simple test functions
    • test_alpha_1, has no named fixtures
    • test_alpha_2, has one named fixture, some_resource
  • test_beta.py
    • similar to test_alpha.py, but with unittest based tests
  • test_gamma.py
    • similar to test_alpha.py, but with class based tests

conftest.py: `

import pytest

@pytest.fixture(scope="session", autouse=True)
def my_own_session_run_at_beginning(request):
    print('\nIn my_own_session_run_at_beginning()')

    def my_own_session_run_at_end():
            print('In my_own_session_run_at_end()')
    request.addfinalizer(my_own_session_run_at_end)


@pytest.fixture(scope="session")
def some_resource(request):
    print('\nIn some_resource()')

    def some_resource_fin():
            print('\nIn some_resource_fin()')
    request.addfinalizer(some_resource_fin)

`

test_alpha.py: `

def test_alpha_1():
    print('\nIn test_alpha_1()')

def test_alpha_2(some_resource):
    print('\nIn test_alpha_2()')

`

test_beta.py: `

import unittest
import pytest

class BetaTest(unittest.TestCase):
    def test_unit_beta_1(self):
        print('\nIn test_unit_beta_1()')

    @pytest.mark.usefixtures('some_resource')    
    def test_unit_beta_2(self):
        print('\nIn test_unit_beta_2()')

`

test_gamma.py: `

class TestGamma:
    def test_gamma_1(self):
        print('\nIn test_gamma_1()')

    def test_gamma_2(self, some_resource):
        print('\nIn test_gamma_2()')

`

Output

Run with pytest -s -v

============================= test session starts ==============================
platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collecting ... collected 6 items

test_alpha.py:1: test_alpha_1 
In my_own_session_run_at_beginning()

In test_alpha_1()
PASSED
test_alpha.py:4: test_alpha_2 
In some_resource()

In test_alpha_2()
PASSED
test_beta.py:5: BetaTest.test_unit_beta_1 
In test_unit_beta_1()
PASSED
test_beta.py:8: BetaTest.test_unit_beta_2 
In test_unit_beta_2()
PASSED
test_gamma.py:2: TestGamma.test_gamma_1 
In test_gamma_1()
PASSED
test_gamma.py:5: TestGamma.test_gamma_2 
In test_gamma_2()
PASSED
In some_resource_fin()
In my_own_session_run_at_end()
=========================== 6 passed in 0.04 seconds ===========================

Mixing function, module, and session scope

Let’s say I’ve got:

  • a function scope fixture ‘resource_c’
  • that uses a module scoped fixture ‘fixture_b’
  • that uses a session scoped fixture ‘fixture_a’

This all works fine.
Also in this example, I’ve added a few autouse fixtures just for fun.

conftest.py: `

import pytest

@pytest.fixture(scope="session")
def resource_a(request):
    print('In resource_a()')

    def resource_a_fin():
            print('\nIn resource_a_fin()')
    request.addfinalizer(resource_a_fin)

@pytest.fixture(scope="module")
def resource_b(request, resource_a):
    print('In resource_b()')

    def resource_b_fin():
            print('\nIn resource_b_fin()')
    request.addfinalizer(resource_b_fin)

@pytest.fixture(scope="function")
def resource_c(request, resource_b):
    print('In resource_c()')

    def resource_c_fin():
            print('\nIn resource_c_fin()')
    request.addfinalizer(resource_c_fin)


# these are just some fun dividiers to make the output pretty
# completely unnecessary, I was just playing with autouse fixtures
@pytest.fixture(scope="function", autouse=True)
def divider_function(request):
    print('\n        --- function %s() start ---' % request.function.__name__)
    def fin():
            print('        --- function %s() done ---' % request.function.__name__)
    request.addfinalizer(fin)

@pytest.fixture(scope="module", autouse=True)
def divider_module(request):
    print('\n    ------- module %s start ---------' % request.module.__name__)
    def fin():
            print('    ------- module %s done ---------' % request.module.__name__)
    request.addfinalizer(fin)

@pytest.fixture(scope="session", autouse=True)
def divider_session(request):
    print('\n----------- session start ---------------')
    def fin():
            print('----------- session done ---------------')
    request.addfinalizer(fin)

`

test_one_two.py: `

def test_one(resource_c):
    print('In test_one()')

def test_two(resource_c):
    print('\nIn test_two()')

`

test_three_four.py: `

def test_three(resource_c):
    print('\nIn test_three()')

def test_four(resource_c):
    print('\nIn test_four()')

`

This seems reasonable to me.
What do you think will happen?

output: `

$ py.test -s -v
==================================== test session starts ====================================
platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collected 4 items 

test_one_two.py:1: test_one 
----------- session start ---------------

    ------- module test_one_two start ---------

        --- function test_one() start ---
In resource_a()
In resource_b()
In resource_c()
In test_one()
PASSED
In resource_c_fin()
        --- function test_one() done ---

test_one_two.py:4: test_two 
        --- function test_two() start ---
In resource_c()

In test_two()
PASSED
In resource_c_fin()
        --- function test_two() done ---

In resource_b_fin()
    ------- module test_one_two done ---------

test_three_four.py:1: test_three 
    ------- module test_three_four start ---------

        --- function test_three() start ---
In resource_b()
In resource_c()

In test_three()
PASSED
In resource_c_fin()
        --- function test_three() done ---

test_three_four.py:4: test_four 
        --- function test_four() start ---
In resource_c()

In test_four()
PASSED
In resource_c_fin()
        --- function test_four() done ---

In resource_b_fin()
    ------- module test_three_four done ---------

In resource_a_fin()
----------- session done ---------------


================================= 4 passed in 0.02 seconds ==================================

`

WARNING: you gotta use bigger and bigger scope

If you do this in the wrong order, things go haywire.

Let’s swap scope on a couple of items.

conftest.py: `

...
@pytest.fixture(scope="module")   # session -> module
def resource_a(request):
    print('In resource_a()')

    def resource_a_fin():
            print('\nIn resource_a_fin()')
    request.addfinalizer(resource_a_fin)

@pytest.fixture(scope="session")   # module -> session
def resource_b(request, resource_a):
...

`

We will get some warning like this (or several): `

E           ScopeMismatchError: You tried to access the 'module' scoped fixture 'resource_a' 
... with a 'session' scoped request object, involved factories
E           conftest.py:18:  def resource_c(request, resource_b)
E           conftest.py:10:  def resource_b(request, resource_a)
E           conftest.py:2:  def resource_a(request)

`

So. Don’t do that.

Warning applies to built in fixtures

Pytest includes some built in fixtures. I believe all of them are function scoped.
This means that you cannot use them from anything other than functions or function scoped fixtures.

Taking it further

The code I’ve shown is for simple run at the beginning and end type fixtures.
However, there’s more you can do with session fixtures.
The pytest.org site has a cool example, A session-fixture which can look at all collected tests.

What are you using session fixtures for?

I’d love to hear examples and use cases for session fixtures.
Please leave a comment or let me know @brianokken of how you are using them.