Videos
The best practice for setup and teardown that run once during whole test run is usage of "session" scope fixture. You should not use hooks for it.
In your case I believe you just need to create conftest.py file in your tests root directory. Then you add some function that you must decorate with @pytest.fixture(scope="session").
Your setup and teardown must be separated with yield statement like this:
import pytest
@pytest.fixture(scope="session")
def some_function_name():
print("this is setup")
yield
print("this is teardown")
Do not forget to request this fixture in every test that needs these setup and teardown. If you need it for all of your tests without any exceptions then you may use flag autouse=True as pytest.fixture argument. However, I do not recommend to use it because it makes your code a bit harder to debug.
Please note that it will be executed only once per whole test run. That's because we use scope "session".
You can read more about fixture scopes here: https://docs.pytest.org/en/6.2.x/fixture.html#scope-sharing-fixtures-across-classes-modules-packages-or-session
UPD:
pytest test_2.py -s
In pytest_addoption
In pytest_configure
In pytest_sessionstart
==== test session starts ===
platform win32 -- Python 3.11.3, pytest-7.3.1, pluggy-1.0.0
rootdir: C:\repos\pytest_imports
configfile: pyproject.toml
collected 2 items
test_2.py
Beginning of fixture
Between test
test1
.
Between test
test2
.
End of fixture
In pytest_sessionfinish
tl;dr: Best practice-wise, according to the PyTest documentation (and also how I do it in all my pytest implementation as well...), is to use the yield fixture. Referred in the docs here, and SO as well.
PyTest recommendation
Reference: https://docs.pytest.org/en/latest/how-to/fixtures.html#teardown-cleanup-aka-fixture-finalization
You use the fixtures for reusability. Although what pl3b said is the better choice, by using the conftest.py, for usage in other test modules, I am showing below, the snippet if you're to try it out in only one module.
There's also the advantage of this fixture, to use specific reusable functions for certain tests. In this example, test_2 and test_3 will use the add_space fixture, and test_1 doesn't.
# ./test_playground.py
import pytest
import inspect
@pytest.fixture(scope="session", autouse=True)
def print_beginning_of_session():
print("I begin here")
yield
print("I end here")
@pytest.fixture(scope="function")
def add_space():
print("--*--")
yield
print("--##--")
def test_no_1():
print(f"this is {inspect.currentframe().f_code.co_name}")
def test_no_2(add_space):
print(f"this is {inspect.currentframe().f_code.co_name}")
def test_no_3(add_space):
print(f"this is {inspect.currentframe().f_code.co_name}")
XUnit-Style
Reference: https://docs.pytest.org/en/stable/how-to/xunit_setup.html
The disadvantage that I see in my example here, in comparison to the fixture-stype above, is that I can't customize the setup_function to only run for test_2 and test_3. There is a way, by splitting it up in test classes, tbf, but it does get a slightly bit more complicated while writing.
# ./test_playground.py
import inspect
# There isn't a setup_session() as far as I understand, so we use module here instead, for a "one-time" start.
def setup_module():
print("I begin here")
def teardown_module():
print("I end here")
def setup_function():
print("--***--")
def teardown_function():
print("--###--")
def test_no_1():
print(f"this is {inspect.currentframe().f_code.co_name}")
def test_no_2():
print(f"this is {inspect.currentframe().f_code.co_name}")
def test_no_3():
print(f"this is {inspect.currentframe().f_code.co_name}")
Output of both styles
FYI: Please forgive the output, it's not really readable for debugging, but it does show where things are. I used the arguments ['-s', '-vv']
============================= test session starts =============================
test_playground.py::test_no_1 I begin here
this is test_no_1
PASSED
test_playground.py::test_no_2 --*--
this is test_no_2
PASSED--##--
test_playground.py::test_no_3 --*--
this is test_no_3
PASSED--##--
I end here