Efficient use of pytest fixtures

Required boilerplate for using pytest inside notebooks.

In [1]:
# Let's make sure pytest and ipytest packages are installed
# ipytest is required for running pytest inside Jupyter notebooks
import sys
!{sys.executable} -m pip install pytest 'ipytest>=0.3.0'

import pytest
from ipytest import magics, clean_tests
__file__ = 'pytest_fixtures.ipynb'
Requirement already satisfied: pytest in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (3.5.0)
Requirement already satisfied: ipytest>=0.3.0 in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (0.3.0)
Requirement already satisfied: attrs>=17.4.0 in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (from pytest) (17.4.0)
Requirement already satisfied: pluggy<0.7,>=0.5 in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (from pytest) (0.6.0)
Requirement already satisfied: setuptools in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (from pytest) (39.0.1)
Requirement already satisfied: py>=1.5.0 in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (from pytest) (1.5.3)
Requirement already satisfied: more-itertools>=4.0.0 in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (from pytest) (4.1.0)
Requirement already satisfied: six>=1.10.0 in /Users/jerry/.virtualenvs/learn-python3/lib/python3.5/site-packages (from pytest) (1.11.0)

Parametrizing fixtures

Similarly as you can parametrize test functions with pytest.mark.parametrize, you can parametrize fixtures:

In [2]:
PATHS = ['/foo/bar.txt', '/bar/baz.txt']

@pytest.fixture(params=PATHS)
def executable(request):
    return request.param
In [3]:
%%run_pytest[clean] '-s'

def test_something_with_executable(executable):
    print(executable)
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 2 items

pytest_fixtures.py /foo/bar.txt
./bar/baz.txt
.

============================================================================== 2 passed in 0.02 seconds ==============================================================================

pytest.mark.usefixtures

pytest.mark.usefixtures is useful especially when you want to use some fixture in a set of tests but you don't need the return value of the fixture.

In [4]:
@pytest.fixture
def my_fixture():
    print('\nmy_fixture is used')

@pytest.fixture
def other_fixture():
    return 'FOO'
In [5]:
%%run_pytest[clean] '-s'

@pytest.mark.usefixtures('my_fixture')
class TestMyStuff:
    def test_somestuff(self):
        pass
    
    def test_some_other_stuff(self, other_fixture):
        print('here we use also other_fixture which returns: {}'.format(other_fixture))
        pass
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 2 items

pytest_fixtures.py 
my_fixture is used
.
my_fixture is used
here we use also other_fixture which returns: FOO
.

================================================================================== warnings summary ==================================================================================
None
  Module already imported so cannot be rewritten: nbval

-- Docs: http://doc.pytest.org/en/latest/warnings.html
======================================================================== 2 passed, 1 warnings in 0.02 seconds ========================================================================
In [6]:
# Needed to clean up test classes (Test*) in ipytest
clean_tests('Test*')

pytest built-in fixtures

Here are a couple of examples of the useful built-in fixtures, you can view all available fixtures by running pytest --fixtures.

monkeypatch

Built-in monkeypatch fixture lets you e.g. set environment variables and set/delete attributes of objects. The use cases are similar as with patching/mocking with unittest.mock.patch/unittest.mock.MagicMock which are part of the Python Standard Library.

Monkeypatching environment variables:

In [7]:
import os

def get_env_var_or_none(var_name):
    return os.environ.get(var_name, None)
In [8]:
%%run_pytest[clean] '-s'

def test_get_env_var_or_none_with_valid_env_var(monkeypatch):
    monkeypatch.setenv('MY_ENV_VAR', 'secret')
    res = get_env_var_or_none('MY_ENV_VAR')
    assert res == 'secret'
    
def test_get_env_var_or_none_with_missing_env_var():
    res = get_env_var_or_none('NOT_EXISTING')
    assert res is None
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 2 items

pytest_fixtures.py ..

================================================================================== warnings summary ==================================================================================
None
  Module already imported so cannot be rewritten: nbval

-- Docs: http://doc.pytest.org/en/latest/warnings.html
======================================================================== 2 passed, 1 warnings in 0.02 seconds ========================================================================

Monkeypatching attributes:

In [9]:
class SomeClass:
    some_value = 'some value'
    
    @staticmethod
    def tell_the_truth():
        print('This is the original truth')
In [10]:
def fake_truth():
    print('This is modified truth')

@pytest.fixture
def fake_some_class(monkeypatch): 
    monkeypatch.setattr('__main__.SomeClass.some_value', 'fake value')
    monkeypatch.setattr('__main__.SomeClass.tell_the_truth', fake_truth)
In [11]:
%%run_pytest[clean] '-s'

def test_some_class(fake_some_class):
    print(SomeClass.some_value)
    SomeClass.tell_the_truth()
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 1 item

pytest_fixtures.py fake value
This is modified truth
.

================================================================================== warnings summary ==================================================================================
None
  Module already imported so cannot be rewritten: nbval

-- Docs: http://doc.pytest.org/en/latest/warnings.html
======================================================================== 1 passed, 1 warnings in 0.02 seconds ========================================================================

tmpdir

tmpdir fixture provides functionality for creating temporary files and directories.

In [12]:
def word_count_of_txt_file(file_path):
    with open(file_path, 'r') as f:
        content = f.read()
        return len(content.split())
In [13]:
%%run_pytest[clean] '-s'

def test_word_count(tmpdir):
    test_file = tmpdir.join('test.txt')
    test_file.write('This is example content of seven words')
    res = word_count_of_txt_file(str(test_file)) # str returns the path
    assert res == 7
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 1 item

pytest_fixtures.py .

================================================================================== warnings summary ==================================================================================
None
  Module already imported so cannot be rewritten: nbval

-- Docs: http://doc.pytest.org/en/latest/warnings.html
======================================================================== 1 passed, 1 warnings in 0.02 seconds ========================================================================

Fixture scope

In [14]:
# scope is function also by default
@pytest.fixture(scope='function')
def func_fixture():
    print('\nfunc')
    
@pytest.fixture(scope='module')
def module_fixture():
    print('\nmodule')
    
@pytest.fixture(scope='session')
def session_fixture():
    print('\nsession')  
In [15]:
%%run_pytest[clean] '-s'

def test_something(func_fixture, module_fixture, session_fixture):
    pass

def test_something_else(func_fixture, module_fixture, session_fixture):
    pass
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 2 items

pytest_fixtures.py 
session

module

func
.
func
.

================================================================================== warnings summary ==================================================================================
None
  Module already imported so cannot be rewritten: nbval

-- Docs: http://doc.pytest.org/en/latest/warnings.html
======================================================================== 2 passed, 1 warnings in 0.02 seconds ========================================================================

Setup-teardown behaviour

In [16]:
@pytest.fixture
def some_fixture():
    print('some_fixture is run now')
    yield 'some magical value'
    print('\nthis will be run after test execution, you can do e.g. some clean up here')
In [17]:
%%run_pytest[clean] '-s'

def test_something(some_fixture):
    print('running test_something')
    assert some_fixture == 'some magical value'
    print('test ends here')
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 1 item

pytest_fixtures.py some_fixture is run now
running test_something
test ends here
.
this will be run after test execution, you can do e.g. some clean up here


================================================================================== warnings summary ==================================================================================
None
  Module already imported so cannot be rewritten: nbval

-- Docs: http://doc.pytest.org/en/latest/warnings.html
======================================================================== 1 passed, 1 warnings in 0.02 seconds ========================================================================

Using fixtures automatically

In [18]:
@pytest.fixture(autouse=True)
def my_fixture():
    print('\nusing my_fixture')
In [19]:
%%run_pytest[clean] '-s'

def test_1():
    pass
    
def test_2():
    pass
================================================================================ test session starts =================================================================================
platform darwin -- Python 3.5.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/jerry/github/jerry-git/learn-python3, inifile: pytest.ini
plugins: nbval-0.9.0
collected 2 items

pytest_fixtures.py 
using my_fixture
.
using my_fixture
.

================================================================================== warnings summary ==================================================================================
None
  Module already imported so cannot be rewritten: nbval

-- Docs: http://doc.pytest.org/en/latest/warnings.html
======================================================================== 2 passed, 1 warnings in 0.02 seconds ========================================================================