Understanding pytest Fixtures and conftest.py for Test Setup
Fixtures in pytest alow defining reusable setup and teardown logic for test cases, serving as shared resources across tests.
Fixture Usage as Function Parameters
Fixtures can be passed directly as parameters to test functions:
import pytest
@pytest.fixture()
def setup_action():
print('\nInitializing test setup')
def test_case_a(setup_action):
print('--- Executing test case A ---')
class TestSuite:
def test_case_b(self, setup_action):
print('--- Executing test case B ---')
if __name__ == '__main__':
pytest.main(["-s", "-v", "-q", "test_fixtures.py"])
Applying Fixtures with @pytest.mark.usefixtures
The @pytest.mark.usefixtures decorator attaches fixtures to test functions or classes:
import pytest
@pytest.fixture()
def setup_action():
print('\nInitializing test setup')
@pytest.mark.usefixtures('setup_action')
def test_case_a():
print('--- Executing test case A ---')
@pytest.mark.usefixtures('setup_action')
class TestSuite:
def test_case_b(self):
print('--- Executing test case B ---')
def test_case_c(self):
print('--- Executing test case C ---')
if __name__ == '__main__':
pytest.main(["-s", "-v", "-q", "test_fixtures_decorator.py"])
Key Differences Between Parameter Passing and uesfixtures:
- When a fixture returns data,
usefixturescannot access the return value, while parameter passing allows direct usage. - For fixtures with return values, pass them as parameters; to fixtures without returns, either method works.
Fixture Configuration Parameters
Fixtures support several configuration options:
import pytest
param_list = ["data1", "data2"]
@pytest.fixture(scope="function", params=param_list, autouse=True, ids=['scenario1', 'scenario2'], name='fixture_example')
def configured_fixture(request):
print(request.param)
def test_scenario_one():
print("\nRunning first test scenario")
def test_scenario_two():
print("\nRunning second test scenario")
if __name__ == '__main__':
pytest.main(["-s", "-v", "-q", "test_fixture_params.py"])
Parameter Descriptions:
scope: Defines fixture lifetime - "function" (default), "class", "module", or "session".params: List of values that trigger multiple fixture executions, generating separate test cases.autouse: When True, fixtures automatically apply to all tests in scope; default is False.ids: Custom identifiers for parameterized tests; auto-generated if not specified.name: Custom fixture name; useful for avoiding naming conflicts.sessionscope: Fixture persists throughout the antire test run.
Sharing Fixtures with conftest.py
The conftest.py file enables fixture sharing across multiple test modules, useful for global setup like authentication or database connections.
Project Structure:
project_root/
├── conftest.py
├── test_module_a.py
└── test_module_b.py
conftest.py Contents:
import pytest
from selenium import webdriver
import requests
import time
@pytest.fixture(scope="session")
def session_setup():
driver = webdriver.Chrome()
driver.implicitly_wait(30)
base_url = "http://www.example.com/"
session = requests.Session()
yield driver, session, base_url
print('Closing browser driver')
driver.quit()
print('Closing request session')
time.sleep(2)
session.close()
@pytest.fixture(scope="function", autouse=True)
def database_connection():
print('Establishing database connection')
pass
def pytest_configure(config):
config.addinivalue_line(
"markers", "web_test"
)
Test Module Example:
import pytest
import time
@pytest.mark.web_test
class WebTestSuite:
@pytest.mark.parametrize('search_input,expected_result', [('test', 'test'), ('example', 'example')])
def test_search_functionality(self, session_setup, search_input, expected_result):
driver, session, base_url = session_setup
time.sleep(2)
driver.get(base_url)
driver.find_element_by_id("search_box").send_keys(search_input)
driver.find_element_by_id("search_button").click()
time.sleep(2)
result = driver.find_element_by_xpath('//*[@id="result_1"]/h3/a').get_attribute('innerHTML')
print(result)
assert expected_result in result
if __name__ == '__main__':
pytest.main([])
Additional Test Module:
class AnotherTestSuite:
def test_using_shared_fixture(self, session_setup):
print('\nTest data: {}'.format(session_setup))
Execute tests from the project root with:
pytest -s -q --tb=no
Observations:
- The
database_connectionfixture executes for each test function due toscope="function"andautouse=True. - The
session_setupfixture runs once per test sesssion. - The
yieldstatement separates pre-test setup (before yield) from post-test teardown (after yield).