Change a way fixtures are called in pytest - python

I am having a fixture in conftest.py
#pytest.fixture(scope="function", autouse=True)
#pytest.mark.usefixtures
def pause_on_assert():
yield
if hasattr(sys, 'last_value') and isinstance(sys.last_value, AssertionError):
tkinter.messagebox.showinfo(sys.last_value)
Similarly there are many other fixures in the conftest.py with scope as session, module
My test cases looks like this
test.py
#pytest.fixture(scope="function", autouse=True)
def _wrapper:
print("pre condition")
yield
print("post condition")
def test_abc():
assert 1==0
Problem is I want my fixture that is there in conftest.py to run before yield of fixture that is in my testcase
How can i change order in the way fixture are executed

Here is an example of running my conftest.py function that prints "A" before my test function that prints "B".
cd to the parent directory, for this example it is py_tests and run.
pytest -s -v
The output is:
A
setting up
B
PASSED
With directory structure:
py_tests
-conftest.py
-tests
--tests.py
Files:
conftest.py
import pytest
#pytest.fixture(scope="function")
def print_one():
print("\n")
print("A")
test.py
import pytest
class Testonething:
#pytest.fixture(scope="function", autouse=True)
def setup(self, print_one):
print("setting up")
def test_one_thing(self):
print("B")
assert True

If you want to ensure a piece of code runs after the test function, but before all fixtures teardown, I'd advise to use the pytest_runtest_teardown hook instead. Replace the pause_on_assert fixture in your conftest.py with:
def pytest_runtest_teardown(item, nextitem):
if hasattr(sys, 'last_value') and isinstance(sys.last_value, AssertionError):
tkinter.messagebox.showinfo(sys.last_value)

Since your _wrapper is a function-scoped autouse fixture: it will be instantiated before other fixtures within the same scope. So, hot fix is to define _wrapper without autouse=True and try to call that decorator implicitly like:
def test_abc(_wrapper):
assert 1==0
Autouse source
[Update] If you don't have an ability to change your test suites, I suggest you just wipe all local specific _wrapper and refactor your conftest-specified fixture to call _wrapper, because fixture functions can use other fixtures themselves. Your conftest.py will be look like this:
# conftest.py
#pytest.fixture(scope="function", autouse=True)
def _wrapper(pause_on_assert):
print("pre condition")
yield
print("post condition")
#pytest.fixture()
def pause_on_assert():
yield
if hasattr(sys, 'last_value') and isinstance(sys.last_value, AssertionError):
tkinter.messagebox.showinfo(sys.last_value)
Modularity source

Related

Global fixtures pytest

Why all fixtures in pytest are global?
For example:
tests/
conftest.py
# content of tests/conftest.py
import pytest
#pytest.fixture
def fixture_1():
pass
test_something.py
# content of tests/test_something.py
def test_something(fixture_2):
assert fixture_2 != None
subfolder/
conftest.py
# content of tests/subfolder/conftest.py
import pytest
#pytest.fixture
def fixture_2(username):
pass
As you can see I can get fixture_2 from second level in test from first level folder.
Is any possibility to set fixture just for one package?

how to override a pytest fixture, but still be able to access it?

I have a conftest.py and a plugin, both defining the same fixture with different implementations:
conftest.py
import pytest
#pytest.fixture
def f():
yield 1
plugin
import pytest
#pytest.fixture
def f():
yield 2
when installing the plugin, the conftest still overrides the plugin, so a test file will only see the conftest fixture, i.e.
test_.py
def test(f):
assert f == 1 # True
I want to be able to do something like this:
If the plugin is not installed, continue
Else, from the conftest plugin, yield the value of the plugin's fixture
I managed to get half of the way:
conftest.py
import pytest
#pytest.fixture
def f(pytestconfig):
if pytestconfig.pluginmanager.has_plugin(plugin_name):
# now what? I have get_plugin and import_plugin, but I'm not able to get the fixture from there...
The easiest way I see is to try and get the plugin fixture value. If the fixture lookup fails, then no plugin defined it and you can do your own thing. Example:
import pytest
from _pytest.fixtures import FixtureLookupError
#pytest.fixture
def f(request):
try: # try finding an already declared fixture with that name
yield request.getfixturevalue('f')
except FixtureLookupError:
# fixture not found, we are the only fixture named 'f'
yield 1

Using fixtures in Pytest

I have the following code in the conftest.py file
#pytest.fixture(scope="session", autouse=True)
def app(request):
global fixture
browser = request.config.getoption("--browser")
base_url=target['baseUrl'])
fixture = Application(browser=browser,base_url=web_config['baseUrl'])
print("\n BEFORE SESSION")
fixture.session.login()
return fixture
#pytest.fixture(scope="session", autouse=True)
def stop(request):
def fin():
print("\n AFTER SESSION")
fixture.session.ensure_logout()
fixture.destroy()
request.addfinalizer(fin)
return fixture
The test file looks like this. Ie fixture, I obviously do not call.
import pytest
#pytest.yield_fixture()
def setup_method():
print("\n BEFORE METHOD")
yield
print("\n AFTER METHOD")
#pytest.mark.usefixtures("setup_method")
def test_add_text_element(app):
print("\n RUN TEST")
app.element.add_blank_page()
app.element.add_element(element_name='Header')
But what if I need to set some other class settings? If I get another fixture, how can I use it in the test file, instead of the one used now?
All about fixtures in py.test you can find in this doc. Below you can found an example how to use fixtures. First of all don't use global. Then be careful about autouse parameter of fixtures. For setup and teardown yield_fixture will be you choice. Use usefixtures as decorator for class. Class will be good to organize your test code. You can found more information about usage in this article (RUS)
conftest.py
#pytest.yield_fixture()
def destroy_method():
yield
print("\n DESTROY")
#pytest.yield_fixture(scope="session", autouse=True)
def app(request):
browser = request.config.getoption("--browser")
fixture = Application(browser=browser, base_url=web_config['baseUrl'])
print("\n BEFORE SESSION")
fixture.session.login()
yield fixture
print("\n AFTER SESSION")
fixture.session.ensure_logout()
fixture.destroy()
Test file will be looks like:
#pytest.yield_fixture()
def setup_method():
print("\n BEFORE METHOD")
yield
print("\n AFTER METHOD")
#pytest.fixture()
def fix1():
return 1
#pytest.fixture()
def fix2():
return 2
#pytest.mark.usefixtures("setup_method", "destroy_method")
class TestSuiteA:
def test_add_text_element(self, fix1, fix2):
print("\n RUN TEST")
assert fix1 + 1 == fix2
From pytest docs:
"yield_fixture" functions:
Since pytest-3.0, fixtures using
the normal fixture decorator can use a yield statement to provide
fixture values and execute teardown code, exactly like yield_fixture
in previous versions.
Marking functions as yield_fixture is still supported, but deprecated
and should not be used in new code.
Reference: https://docs.pytest.org/en/latest/yieldfixture.html

pytest: Adding fixture in the test file instead of conftest.py

I am new to Python and I have a doubt in pytest
test_client.py
# Simple Example tests
import pytest
def test_one():
assert False == False
def test_two():
assert True == True
def cleanup():
# do some cleanup stuff
conftest.py
import pytest
import test_client
#pytest.fixture(scope="session", autouse=True)
def do_clean_up(request):
request.addfinalizer(test_client.cleanup)
Is it possible to move the fixture defined in conftest.py to the test_client.py thereby eliminating having conftest.py
Yes. Why didn't you simply try? ;)
Fixtures are put in conftest.py files to be able to use them in multiple test files.

How to call setup once for all tests and teardown after all are finished

I have a bunch of tests written using pytest. There are all under a directory dir. For example:
dir/test_base.py
dir/test_something.py
dir/test_something2.py
...
The simplified version of code in them is as follows:
test_base.py
import pytest
class TestBase:
def setup_module(module):
assert False
def teardown_module(module):
assert False
test_something.py
import pytest
from test_base import TestBase
class TestSomething(TestBase):
def test_dummy():
pass
test_something2.py
import pytest
from test_base import TestBase
class TestSomethingElse(TestBase):
def test_dummy2():
pass
All my test_something*.py files extend the base class in test_base.py. Now I wrote setup_module(module) and teardown_module(module) methods in test_base.py. I was expecting the setup_module to be called once for all tests, and teardown_module() to be called at the end, once all tests are finished.
But the functions don’t seem to be getting called? Do I need some decorators for this to work?
The OP's requirement was for setup and teardown each to execute only once, not one time per module. This can be accomplished with a combination of a conftest.py file, #pytest.fixture(scope="session") and passing the fixture name to each test function.
These are described in the Pytest fixtures documentation
Here's an example:
conftest.py
import pytest
#pytest.fixture(scope="session")
def my_setup(request):
print '\nDoing setup'
def fin():
print ("\nDoing teardown")
request.addfinalizer(fin)
test_something.py
def test_dummy(my_setup):
print '\ntest_dummy'
test_something2.py
def test_dummy2(my_setup):
print '\ntest_dummy2'
def test_dummy3(my_setup):
print '\ntest_dummy3'
The output when you run py.test -s:
collected 3 items
test_something.py
Doing setup
test_dummy
.
test_something2.py
test_dummy2
.
test_dummy3
.
Doing teardown
The name conftest.py matters: you can't give this file a different name and expect Pytest to find it as a source of fixtures.
Setting scope="session" is important. Otherwise setup and teardown will be repeated for each test module.
If you'd prefer not to pass the fixture name my_setup as an argument to each test function, you can place test functions inside a class and apply the pytest.mark.usefixtures decorator to the class.
Put setup_module and teardown_module outside of a class on module level. Then add your class with your tests.
def setup_module(module):
"""..."""
def teardown_module(module):
"""..."""
class TestSomething:
def test_dummy(self):
"""do some tests"""
For more info refer to this article.
setup_module/teardown_module are called for the module where the eventual (derived) tests are defined. This also allows to customize the setup. If you only ever have one setup_module you can put it to test_base.py and import it from the other places. HTH.
First of all it is a good practice to put all tests in a module called "tests":
<product>
...
tests/
__init__.py
test_something.py
Secondly I think you should use setup_class/teardown_class methods in your base class:
import unittest
class MyBase(unittest.TestCase):
#classmethod
def setup_class(cls):
...
#classmethod
def teardown_class(cls):
...
More info: http://pytest.org/latest/xunit_setup.html

Categories