Fixture order of execution in pytest - python

I'm new to using Pytest. I'm a little confused about what's the appropriate use of fixtures and dependency injection for my project. My framework is the following:
conftest.py
#pytest.fixture(scope="session")
def test_env(request):
//some setup here for the entire test module test_suite.py
#pytest.fixture(scope="function")
def setup_test_suite(test_env):
//some setup that needs to be done for each test function
//eventually parameterize? for different input configuration
test_suite.py
def test_function(setup_test_suite)
//some testing code here
What's the difference between putting the setup function in conftest.py or within the test suite itself?
I want the setup of the testing environment to be done once, for the entire session. If setup_test_suite is dependent on test_env, will test_env execute multiple times?
If I were to parameterize setup_test_suite, what would be the order of calling the fixtures?

The difference between a fixture in conftest.py and in the local test module is visibility. The fixture will be visible to all test modules at the same levels and in modules below that level. If the fixture is set to autouse=True it will be executed for all tests in these modules.
For more information, check What is the use of conftest.py files
If test_env is session-based, it will be executed only once in the session, even if it is referenced in multiple tests. If it yields an object, the same object will be passed to all tests referencing it.
For example:
#pytest.fixture(scope="session")
def global_fixt():
print('global fixture')
yield 42
#pytest.fixture(scope="function")
def local_fixt(global_fixt):
print('local fixture')
yield 5
def test_1(global_fixt, local_fixt):
print(global_fixt, local_fixt)
def test_2(global_fixt, local_fixt):
print(global_fixt, local_fixt)
Gives the output for pytest -svv:
...
collected 2 items
test_global_local_fixtures.py::test_1 global fixture
local fixture
42 5
PASSED
test_global_local_fixtures.py::test_2 local fixture
42 5
PASSED
...
Fixture parameters are called in the order they are provided to the fixture. if you have for example:
#pytest.fixture(scope="session", params=[100, 1, 42])
def global_fixt(request):
yield request.param
def test_1(global_fixt):
assert True
def test_2(global_fixt):
assert True
The tests will be executed in this order:
test_1 (100)
test_2 (100)
test_1 (1)
test_2 (1)
test_1 (42)
test_2 (42)

Related

override autouse=True pytest fixture [duplicate]

Let's say I have a very simple logging decorator:
from functools import wraps
def my_decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__} ran with args: {args}, and kwargs: {kwargs}")
result = func(*args, **kwargs)
return result
return wrapper
I can add this decorator to every pytest unit test individually:
#my_decorator
def test_one():
assert True
#my_decorator
def test_two():
assert 1
How can I automatically add this decorator to every single pytest unit test so I don't have to add it manually? What if I want to add it to every unit test in a file? Or in a module?
My use case is to wrap every test function with a SQL profiler, so inefficient ORM code raises an error. Using a pytest fixture should work, but I have thousands of tests so it would be nice to apply the wrapper automatically instead of adding the fixture to every single test. Additionally, there may be a module or two I don't want to profile so being able to opt-in or opt-out an entire file or module would be helpful.
Provided you can move the logic into a fixture, as stated in the question, you can just use an auto-use fixture defined in the top-level conftest.py.
To add the possibility to opt out for some tests, you can define a marker that will be added to the tests that should not use the fixture, and check that marker in the fixture, e.g. something like this:
conftest.py
import pytest
def pytest_configure(config):
config.addinivalue_line(
"markers",
"no_profiling: mark test to not use sql profiling"
)
#pytest.fixture(autouse=True)
def sql_profiling(request):
if not request.node.get_closest_marker("no_profiling"):
# do the profiling
yield
test.py
import pytest
def test1():
pass # will use profiling
#pytest.mark.no_profiling
def test2():
pass # will not use profiling
As pointed out by #hoefling, you could also disable the fixture for a whole module by adding:
pytestmark = pytest.mark.no_profiling
in the module. That will add the marker to all contained tests.

Can pytest fixtures be combined?

Can one fixture build on another in pytest? I have a very simple fixture called "cleaner" defined as:
import pytest
from mypackage import db
#pytest.fixture()
def cleaner(request):
def finalizer():
db.clear()
request.addfinalizer(finalizer)
then in my setup.cfg I have:
[pytest]
norecursedirs = .git venv
usefixtures = cleaner
This results in the database being truncated after each test, which is great. But now I want other fixtures I make to also call the finalizer from cleaner. Is there a way to define another fixture that somehow expands on or calls cleaner?
You have to declare that your other fixture depends on cleaner explicitly:
import pytest
#pytest.fixture
def cleaner(request):
def finalizer():
print '\n"cleaner" finalized'
print '\n"cleaner" fixture'
request.addfinalizer(finalizer)
#pytest.fixture
def other(cleaner):
print '\n"other" fixture'
def test_foo(other):
pass
Running this with py.test -s -v produces:
test_foo.py#16::test_foo
"cleaner" fixture
"other" fixture
PASSED
"cleaner" finalized

Why cant unittest.TestCases see my py.test fixtures?

I'm trying to use py.test's fixtures with my unit tests, in conjunction with unittest. I've put several fixtures in a conftest.py file at the top level of the project (as described here), decorated them with #pytest.fixture, and put their names as arguments to the test functions that require them.
The fixtures are registering correctly, as shown by py.test --fixtures test_stuff.py, but when I run py.test, I get NameError: global name 'my_fixture' is not defined. This appears to only occur when I use subclasses of unittest.TestCase—but the py.test docs seem to say that it plays well with unittest.
Why can't the tests see the fixtures when I use unittest.TestCase?
Doesn't work:
conftest.py
#pytest.fixture
def my_fixture():
return 'This is some fixture data'
test_stuff.py
import unittest
import pytest
class TestWithFixtures(unittest.TestCase):
def test_with_a_fixture(self, my_fixture):
print(my_fixture)
Works:
conftest.py
#pytest.fixture()
def my_fixture():
return 'This is some fixture data'
test_stuff.py
import pytest
class TestWithFixtures:
def test_with_a_fixture(self, my_fixture):
print(my_fixture)
I'm asking this question more out of curiosity; for now I'm just ditching unittest altogether.
While pytest supports receiving fixtures via test function arguments
for non-unittest test methods, unittest.TestCase methods cannot
directly receive fixture function arguments as implementing that is
likely to inflict on the ability to run general unittest.TestCase test
suites.
From the note section at the bottom of:
https://pytest.org/en/latest/unittest.html
It's possible to use fixtures with unittest.TestCasees. See that page for more information.
You can use the pytest fixtures in unittest.TestCase with the pytest option autouse. However, if you use the test_ for the unit method using the fixture the following error will appear:
Fixtures are not meant to be called directly,...
### conftest.py
#pytest.fixture
def my_fixture():
return 'This is some fixture data'
One solution is to use a prepare_fixture method to set fixtures as an attribute of the TestWithFixtures class, so that fixtures are available to all unit test methods.
### test_stuff.py
import unittest
import pytest
class TestWithFixtures(unittest.TestCase):
#pytest.fixture(autouse=True)
def prepare_fixture(self, my_fixture):
self.myfixture = my_fixture
def test_with_a_fixture(self):
print(self.myfixture)
define the fixture as an accessible variable, (like input in the following example). To define it, use request.cls.VARIABLE_NAME_YOU_DEFINE = RETURN_VALUE
use #pytest.mark.usefixtures("YOUR_FIXTURE") to use fixture outside of the unittest class, inside the unittest class, access the fixture by self.VARIABLE_NAME_YOU_DEFINE.
e.g.
import unittest
import pytest
#pytest.fixture(scope="class")
def test_input(request):
request.cls.input = {"key": "value"}
#pytest.mark.usefixtures("test_input")
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertEqual(self.input["key"], "value")

How to run a method before all tests in all classes?

I'm writing selenium tests, with a set of classes, each class containing several tests. Each class currently opens and then closes Firefox, which has two consequences:
super slow, opening firefox takes longer than running the test in a class...
crashes, because after firefox has been closed, trying to reopen it really quickly, from selenium, results in an 'Error 54'
I could solve the error 54, probably, by adding a sleep, but it would still be super slow.
So, what I'd like to do is reuse the same Firefox instances across all test classes. Which means I need to run a method before all test classes, and another method after all test classes. So, 'setup_class' and 'teardown_class' are not sufficient.
Using session fixture as suggested by hpk42 is great solution for many cases,
but fixture will run only after all tests are collected.
Here are two more solutions:
conftest hooks
Write a pytest_configure or pytest_sessionstart hook in your conftest.py file:
# content of conftest.py
def pytest_configure(config):
"""
Allows plugins and conftest files to perform initial configuration.
This hook is called for every plugin and initial conftest
file after command line options have been parsed.
"""
def pytest_sessionstart(session):
"""
Called after the Session object has been created and
before performing collection and entering the run test loop.
"""
def pytest_sessionfinish(session, exitstatus):
"""
Called after whole test run finished, right before
returning the exit status to the system.
"""
def pytest_unconfigure(config):
"""
called before test process is exited.
"""
pytest plugin
Create a pytest plugin with pytest_configure and pytest_unconfigure hooks.
Enable your plugin in conftest.py:
# content of conftest.py
pytest_plugins = [
'plugins.example_plugin',
]
# content of plugins/example_plugin.py
def pytest_configure(config):
pass
def pytest_unconfigure(config):
pass
You might want to use a session-scoped "autouse" fixture:
# content of conftest.py or a tests file (e.g. in your tests or root directory)
#pytest.fixture(scope="session", autouse=True)
def do_something(request):
# prepare something ahead of all tests
request.addfinalizer(finalizer_function)
This will run ahead of all tests. The finalizer will be called after the last test finished.
Starting from version 2.10 there is a cleaner way to tear down the fixture as well as defining its scope. So you may use this syntax:
#pytest.fixture(scope="module", autouse=True)
def my_fixture():
print('INITIALIZATION')
yield param
print('TEAR DOWN')
The autouse parameter:
From documentation:
Here is how autouse fixtures work in other scopes:
autouse fixtures obey the scope= keyword-argument: if an autouse fixture has scope='session' it will only be run once, no matter where
it is defined. scope='class' means it will be run once per class, etc.
if an autouse fixture is defined in a test module, all its test functions automatically use it.
if an autouse fixture is defined in a conftest.py file then all tests in all test modules below its directory will invoke the fixture.
...
The "request" parameter:
Note that the "request" parameter is not necessary for your purpose although you might want to use it for other purposes. From documentation:
"Fixture function can accept the request object to introspect the
“requesting” test function, class or module context.."
Try to use pytest_sessionstart(session) in conftest.py
Example:
# project/tests/conftest.py
def pytest_sessionstart(session):
print('BEFORE')
# project/tests/tests_example/test_sessionstart.py
import pytest
#pytest.fixture(scope='module', autouse=True)
def fixture():
print('FIXTURE')
def test_sessonstart():
print('TEST')
Log:
BEFORE
============================================================================ test session starts =============================================================================
platform darwin -- Python 3.7.0, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /Library/Frameworks/Python.framework/Versions/3.7/bin/python3
cachedir: .pytest_cache
rootdir: /Users/user/Documents/test, inifile: pytest.ini
plugins: allure-pytest-2.8.12, env-0.6.2
collected 1 item
tests/6.1/test_sessionstart.py::test_sessonstart FIXTURE
TEST
PASSED

pytest fixture with scope session running for every test

Correct me if I'm wrong, but if a fixture is defined with scope="session", shouldn't it be run only once per the whole pytest run?
For example:
import pytest
#pytest.fixture
def foo(scope="session"):
print('foooooo')
def test_foo(foo):
assert False
def test_bar(foo):
assert False
I have some tests that rely on data retrieved from some APIs, and instead of querying the API in each test, I rather have a fixture that gets all the data at once, and then each test uses the data it needs. However, I was noticing that for every test, a request was made to the API.
That's because you're declaring the fixture wrong. scope should go into the pytest.fixture decoraror parameters:
#pytest.fixture(scope="session")
def foo():
print('foooooo')
In your code, the scope is left to default value function, that's why the fixture is being ran for each test.

Categories