I’d like to wrap up this recent series of pytest fixture posts by presenting my version of some sort of reference.
Since this post is running a bit long, here are some links to the content buried in here.
- Bare bones example
- Three ways to use a fixture
- Name it, usefixtures, and autouse.
- Usefixtures example
- Fixture features
- Experimental and still to cover
Since I’m planning on using this for my own reference, I’ll throw a couple more links in here at the top to stuff I find useful regarding pytest fixtures.
- pytest tutorial, my original introduction post. I still use it as a reference.
- pytest support for xunit style fixtures, setup/teardown for function, method, module, class. I still use this old style sometimes.
- pytest API, specifically, the fixtures and requests part
And… while I’m throwing links around, here are the other posts in the series:
I’m trying to keep the examples in this post kind of small-ish.
However, I do run the risk of being too terse. (or too verbose).
It’s also possible that the order I’ve laid things out is odd. I’ve done a lot of copy/paste from code editor and bash window to get this post put together.
Please let me know:
- if I’ve mucked up some copy/paste and there is something bizarre in here.
- if I’ve been too terse or unclear.
- if what I’m stating is completely wrong. Especially this one
- if you actually made it to the end without wanting to throw something at me.
Note about common code
For all the examples, the test file I’m running has this at the top:
`
from __future__ import print_function import pytest
`
However, I’m not going to copy it into every code block below.
I’m also running each example with: `
$py.test -s file_name.py
`
Bare bones example
Here’s a super basic fixture, and a couple tests that use it. `
@pytest.fixture() def before(): print('\nbefore each test') def test_1(before): print('test_1()') def test_2(before): print('test_2()')
`
With the default parameters for ‘pytest.fixture()’, the fixture is going to be called for every test that names the fixture in it’s parameter list.
Output: `
before each test test_1() . before each test test_2() .
`
Three ways to use a fixture
-
name it from the test.
Just like the top example -
usefixtures decorator
You can mark a test or a test class with ‘pytest.mark.usefixtures()’ and include a list of fixtures to be used with the test or class of tests.
This is especially convenient when dealing with test classes.
It also is useful when converting unittest classes to use pytest fixtures.
I’ll give an example shortly. -
autouse
Powerful, but possibly dangerous.
Covered in the next section.
Usefixtures example
Here’s a quick example of the decorator.
The first example can be written like this: `
@pytest.mark.usefixtures("before") def test_1(): print('test_1()') @pytest.mark.usefixtures("before") def test_2(): print('test_2()')
`
Or, this: `
class Test: @pytest.mark.usefixtures("before") def test_1(self): print('test_1()') @pytest.mark.usefixtures("before") def test_2(self): print('test_2()')
`
Or, this: `
@pytest.mark.usefixtures("before") class Test: def test_1(self): print('test_1()') def test_2(self): print('test_2()')
`
All with the same effect.
Fixture features
If I fill in the default parameters for ‘pytest.fixture()’ and add a request param to my fixture, it looks like this, but doesn’t run any different. `
@pytest.fixture(scope='function', params=None, autouse=False) def before(request): print('\nbefore()') return None
` Now lets take a look at these features.
- Return value
- Finalizer is teardown
- Request objects
- Scope
- Params
- Autouse
- Multiple fixtures
- Modularity: fixtures using other fixtures
Return value
In the bare bones example, the fixture returns ‘None’.
That’s becuase it’s just some code I want to run before my test, like traditional setup functions from nose or unittest.
However, you can return anything you want from the fixture function.
If your fixture is setting up some data, or reading a file, or opening a connection to a database, then access to that data or resources is what you ought to return from the fixture.
Returning some data from a fixture. `
@pytest.fixture() def some_data(): data = {'foo':1, 'bar':2, 'baz':3} return data def test_foo(some_data): assert some_data['foo'] == 1
`
Returning a database object: `
@pytest.fixture() def cheese_db(request): print('\n[setup] cheese_db, connect to db') # code to connect to your db a_dictionary_for_now = {'Brie': 'No.', 'Camenbert': 'Ah! We have Camenbert, yessir.'} def fin(): print('\n[teardown] cheese_db finalizer, disconnect from db') request.addfinalizer(fin) return a_dictionary_for_now def test_cheese_database(cheese_db): print('in test_cheese_database()') for variety in cheese_db.keys(): print('%s : %s' % (variety, cheese_db[variety])) def test_brie(cheese_db): print('in test_brie()') assert cheese_db['Brie'] == 'No.' def test_camenbert(cheese_db): print('in test_camenbert()') assert cheese_db['Camenbert'] != 'No.'
`
Finalizer is teardown
In the previous code example, the ‘cheese_db’ fixture has this bit of code:
`
def cheese_db(request): ... def fin(): print('\n[teardown] cheese_db finalizer, disconnect from db') request.addfinalizer(fin) ...
`
The ‘fin’ function is acting as the ‘teardown’ for the fixture.
There’s nothing special about the name.
You can name it ‘teardown’ or ‘cheese_db_teardown’ or ‘something_else’.
It doesn’t matter.
The finalizer is called after all of the tests that use the fixture.
If you’ve used parameterized fixtures, the finalizer is called between instances of the parameterized fixture changes.
Scope
Scope controls how often a fixture gets called. The default is “function”.
Here are the options for scope:
function |
class |
module |
session |
Since the default scope is “function”, the cheese db example will open and close the db for every test. `
[setup] cheese_db, connect to db in test_cheese_database() Camenbert : Ah! We have Camenbert, yessir. Brie : No. . [teardown] cheese_db finalizer, disconnect from db [setup] cheese_db, connect to db in test_brie() . [teardown] cheese_db finalizer, disconnect from db [setup] cheese_db, connect to db in test_camenbert() . [teardown] cheese_db finalizer, disconnect from db
`
This doesn’t really make sense. Especially if connecting is a time consuming operation.
Change the scope: `
@pytest.fixture(scope="module") def cheese_db(request): ...
`
And let’s re-run it: `
[setup] cheese_db, connect to db in test_cheese_database() Camenbert : Ah! We have Camenbert, yessir. Brie : No. .in test_brie() .in test_camenbert() . [teardown] cheese_db finalizer, disconnect from db
`
That’s better.
Request objects
In the cheese db example, the fixture includes a request parameter. You need the request parameter to a fixture to add a finilizer.
However, it has other uses too.
In the example below, I’m showing the use (well, printing stuff) of some of the items. See pytest API for a full list.
`
@pytest.fixture() def my_fixture(request): print('\n-----------------') print('fixturename : %s' % request.fixturename) print('scope : %s' % request.scope) print('function : %s' % request.function.__name__) print('cls : %s' % request.cls) print('module : %s' % request.module.__name__) print('fspath : %s' % request.fspath) print('-----------------') if request.function.__name__ == 'test_three': request.applymarker(pytest.mark.xfail) def test_one(my_fixture): print('test_one():') class TestClass(): def test_two(self, my_fixture): print('test_two()') def test_three(my_fixture): print('test_three()') assert False
`
Params
An optional parameter to the fixture decorator is ‘params’.
It defaults to ‘None’.
For each value in params, the fixture will be called with request.param filled in with that value.
Tests that use the fixture will be called once FOR EACH value in params.
Toy example
An example is in order here.
This first example is a silly one, but does show the mechanics, and the utility of both the -v flag and how well py.test deals with failures of parameterized tests.
`
@pytest.fixture( params=[1,2,3] ) def test_data(request): return request.param def test_not_2(test_data): print('test_data: %s' % test_data) assert test_data != 2
`
This should run ‘test_not_2’ three times, and fail when 2 is passed in.
I’ll run it both without and with the -v flag `
> py.test test_params.py ============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 collected 3 items test_params.py .F. ================================== FAILURES =================================== ________________________________ test_not_2[2] ________________________________ test_data = 2 def test_not_2(test_data): print('test_data: %s' % test_data) > assert test_data != 2 E assert 2 != 2 test_params.py:10: AssertionError ------------------------------- Captured stdout ------------------------------- test_data: 2 ===================== 1 failed, 2 passed in 0.02 seconds ====================== > py.test -v test_params.py ============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 -- C:\Python27\python2.7.exe collecting ... collected 3 items test_params.py:8: test_not_2[1] PASSED test_params.py:8: test_not_2[2] FAILED test_params.py:8: test_not_2[3] PASSED ================================== FAILURES =================================== ________________________________ test_not_2[2] ________________________________ test_data = 2 def test_not_2(test_data): print('test_data: %s' % test_data) > assert test_data != 2 E assert 2 != 2 test_params.py:10: AssertionError ------------------------------- Captured stdout ------------------------------- test_data: 2 ===================== 1 failed, 2 passed in 0.02 seconds ======================
`
Real example
Now for a more real world usage of parameterization, input and expected output.
Here’s a rewrite of the markdown test from the pytest introduction post.
The ‘run_markdown’ function is a software API adapter, which takes care of calling the markdown script on the command line.
`
from markdown_adapter import run_markdown @pytest.fixture( params=[ # tuple with (input, expectedOutput) ('regular text' , 'regular text'), ('*em tags*' , 'em tags
'), ('**strong tags**', 'strong tags
') ]) def test_data(request): return request.param def test_markdown(test_data): (the_input, the_expected_output) = test_data the_output = run_markdown(the_input) print('\ntest_markdown():') print(' input : %s' % the_input) print(' output : %s' % the_output) print(' expected: %s' % the_expected_output) assert the_output == the_expected_output
`
The output makes it clear that the one test ‘test_markdown’ is called 3 times.
Of course, the print statements are unnecessary.
I left them in for demo purposes.
`
> py.test -s test_markdown.py ============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 collected 3 items test_markdown.py test_markdown(): input : regular text output : regular text expected: regular text . test_markdown(): input : *em tags* output : em tags expected: em tags . test_markdown(): input : **strong tags** output : strong tags expected: strong tags . ========================== 3 passed in 0.11 seconds ===========================
`
Normally, you don’t need the print statements to see what’s going on.
I’m going to take the print statements out, and change the ‘expected’ string of the last input value to show how py.test is quite helpful in pointing out the data set that fails.
`
@pytest.fixture( params=[ # tuple with (input, expectedOutput) ('regular text' , 'regular text'), ('*em tags*' , 'em tags
'), ('**strong tags**', 'strong tags
') ]) def test_data(request): return request.param def test_markdown(test_data): (the_input, the_expected_output) = test_data the_output = run_markdown(the_input) assert the_output == the_expected_output
`
output: `
> py.test test_markdown2.py ============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 collected 3 items test_markdown2.py ..F ================================== FAILURES =================================== __________________________ test_markdown[test_data2] __________________________ test_data = ('**strong tags**', 'strong tags') def test_markdown(test_data): (the_input, the_expected_output) = test_data the_output = run_markdown(the_input) > assert the_output == the_expected_output E assert 's...
' == 'st...tags
' E -strong tags E ? ^^^^^^ E + strong tags E ? ^^ test_markdown2.py:17: AssertionError ===================== 1 failed, 2 passed in 0.11 seconds ======================
`
Autouse
An optional parameter to the fixture decorator is ‘autouse’.
It defaults to ‘False’.
With the value of ‘False’, tests that wish to use the fixture need to either name it in their parameter list, or have a use fixtures decorator applied to the test.
See the first two items in section ‘three ways to use a fixture‘ for more information.
With the value set to ‘True’, all tests in this session just use the fixture automatically.
Yes, with great power comes great responsibility.
So use it carefully.
However, it is quite handy in places where you would have used the xunit style setup_module
For our example, let’s just say I’ve got some reporting code I’d like to run at the top of module and at the top of a test funcion.
The tests themselves don’t need a handle to the fixtures, since they aren’t returning any data.
`
@pytest.fixture(scope="module", autouse=True) def mod_header(request): print('\n-----------------') print('user : %s' % getpass.getuser()) print('module : %s' % request.module.__name__) print('-----------------') @pytest.fixture(scope="function", autouse=True) def func_header(request): print('\n-----------------') print('function : %s' % request.function.__name__) print('time : %s' % time.asctime()) print('-----------------') def test_one(): print('in test_one()') def test_two(): print('in test_two()')
`
output: `
> py.test -s test_autouse.py ============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 collected 2 items test_autouse.py ----------------- user : okken module : test_autouse ----------------- ----------------- function : test_one time : Tue Feb 04 17:31:20 2014 ----------------- in test_one() . ----------------- function : test_two time : Tue Feb 04 17:31:20 2014 ----------------- in test_two() . ========================== 2 passed in 0.02 seconds ===========================
`
Multiple fixtures
In the examples I’ve used so far, tests only are using at most one named fixture.
You can use more.
Simple example: `
@pytest.fixture(scope="module") def foo(request): print('\nfoo setup - module fixture') def fin(): print('foo teardown - module fixture') request.addfinalizer(fin) @pytest.fixture() def bar(request): print('bar setup - function fixture') def fin(): print('bar teardown - function fixture') request.addfinalizer(fin) @pytest.fixture() def baz(request): print('baz setup - function fixture') def fin(): print('baz teardown - function fixture') request.addfinalizer(fin) def test_one(foo, bar, baz): print('in test_one()') def test_two(foo, bar, baz): print('in test_two()')
`
output: `
> py.test -s test_multiple.py ============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 collected 2 items test_multiple.py foo setup - module fixture bar setup - function fixture baz setup - function fixture in test_one() .baz teardown - function fixture bar teardown - function fixture bar setup - function fixture baz setup - function fixture in test_two() .baz teardown - function fixture bar teardown - function fixture foo teardown - module fixture ========================== 2 passed in 0.01 seconds ===========================
`
Modularity: fixtures using other fixtures
Tests can use one or more fixture.
Fixtures themselves can also use one or more fixtures.
I’ll rewrite the previous example, but instead of having the tests include all foo, bar, and baz fixtures, I’ll chain them together.
And one more wrinkle, ‘test_two’ will only include ‘bar’.
`
@pytest.fixture(scope="module") def foo(request): print('\nfoo setup - module fixture') def fin(): print('foo teardown - module fixture') request.addfinalizer(fin) @pytest.fixture() def bar(request, foo): print('bar setup - function fixture') def fin(): print('bar teardown - function fixture') request.addfinalizer(fin) @pytest.fixture() def baz(request, bar): print('baz setup - function fixture') def fin(): print('baz teardown - function fixture') request.addfinalizer(fin) def test_one(baz): print('in test_one()') def test_two(bar): # only use bar print('in test_two()')
`
output `
> py.test -s test_modular.py ============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 collected 2 items test_modular.py foo setup - module fixture bar setup - function fixture baz setup - function fixture in test_one() .baz teardown - function fixture bar teardown - function fixture bar setup - function fixture in test_two() .bar teardown - function fixture foo teardown - module fixture ========================== 2 passed in 0.02 seconds ===========================
`
Experimental and still to cover
In this section, I’m listing the experimental features, and features I haven’t fully tested and/or don’t quite understand yet.
yield_fixture
Thank you Johannes for pointed out this feature in the comments.
You can use ‘yield_fixture’ instead of the ‘fixture’ decorator for functions that yield their value rather than returning them.
The benefits are:
- It works like a context manager.
- You don’t have to register a teardown function via addfinalizer.
- Therfore, you don’t have to include the request parameter just for the addfinalizer function.
Caveats:
It’s still “experimental”, so supposedly the syntax/behavior might change in the future.As of pytest 2.7.0, it’s no longer considered experimental. It will stay.- Probably more, but I haven’t used it much to know what else to be careful of.
Here’s a quick example. (Yep. Staight from the comment.) `
@pytest.yield_fixture(scope="module") def cheese_db(): print('\n[setup] cheese_db, connect to db') a_dictionary_for_now = {'Brie': 'No.', 'Camenbert': 'Ah! We have Camenbert, yessir.'} yield a_dictionary_for_now print('\n[teardown] cheese_db finalizer, disconnect from db') def test_brie(cheese_db): print('in test_brie()') assert cheese_db['Brie'] == 'No.' def test_camenbert(cheese_db): print('in test_camenbert()') assert cheese_db['Camenbert'] != 'No.'
`
`
$ py.test -s pytestfixtures/test_yield.py ====================== test session starts ====================== platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 collected 2 items pytestfixtures/test_yield.py [setup] cheese_db, connect to db in test_brie() .in test_camenbert() . [teardown] cheese_db finalizer, disconnect from db =================== 2 passed in 0.01 seconds ===================
`
WARNING: My recommendation is to be aware of this feature, but use ‘addfinalizer’ for production test code.
This is a cool feature. But since it’s still listed as ‘experimental’, and I haven’t done much testing with it or testing of it, I can’t in good conscience recommend it’s use.
Hey pytest devs: Let me know if this WARNING is too strong.
I DO recommend you use it IFF you are either solo or on a small team where you are able to easily change the test code in the future if the syntax/behavior changes in future pytest releases.
ids
More information at pytest.org
I played around with this a bit, but couldn’t get anything to work.
I’m sure I was just doing something wrong.
If anyone has a working example they could share, please do.
Feedback
Leave a comment. Let me know if this is helpful or confusing. If you end up finding some bell or whistle of pytest fixtures that I missed, let me know.