pytest_sessionstart and pytest_sessionfinish (A Complete Guide)
Imagine kicking off a lengthy test suite, only to find they all failed because a database wasn’t accessible or an API endpoint was down.
Frustrating, right?
Maybe you’ve experienced this scenario before, where a single failure cascades into multiple test failures, wasting time, resources, and increasing costs in your CI/CD pipelines.
But what if you could check critical resources and dependencies before running your tests, to avoid such situations?
How about if you wanted your tests to do something before collection - maybe ping a webhook or send a Slack message?
This is where Pytest’s session start and session finish hooks come to the rescue.
These hooks allow you to pre-check critical resources and dependencies or execute custom code before any tests run, ensuring you catch issues early and avoid a cascade of failures.
In this article, we’ll dive into Pytest’s session management hooks, exploring how they can save you time and headaches by validating your test environment upfront.
We’ll cover what these hooks are, why they’re essential, and how to implement them effectively.
Say goodbye to wasted test runs and hello to more reliable testing processes with pytest_sessionstart
and pytest_sessionfinish
hooks.
Let’s get started.
What You’ll Learn
By the end of this article you’ll be able to:
- Understand and implement Pytest session management hooks.
- Verify resources before the test execution begins.
- Verify and pass custom CLI arguments to tests during session management.
Session Management Hooks in Pytest
Pytest’s session management hooks let you run custom steps before test collection.
This approach is extremely valuable when you need to perform pre-checks on resources and dependencies before the test session begins.
The two powerful session management hooks in Pytest are pytest_sessionstart
and pytest_sessionfinish
.
These hooks help you manage resources effectively, building a robust, scalable, and maintainable test suite that efficiently handles complex test environments.
pytest_sessionstart(session):
Thepytest_sessionstart
hook is called after theSession
object is created and before test collection and execution begins. This is the perfect moment to perform checks like database connectivity and API availability to ensure everything is in place for your tests to run smoothly.pytest_sessionfinish(session, exitstatus):
Thepytest_sessionfinish
hook is called after the test session has completed, right before Pytest returns the exit status. This allows you to clean up resources and handle any final tasks necessary after all tests have been executed, perhaps generate reports or send Slack notifications.
Effective session management is crucial for optimizing test performance and resource utilization, especially in complex test environments. Be aware that a few exceptions can be raised when creating a session object:
- Interrupted: Indicates an interruption during the session object creation.
- Failed: Indicates a failure to create the session object.
By leveraging session management hooks, you can ensure your test environment is always in top shape, preventing cascading failures and enhancing test reliability.
Importance of Session Management
Understanding session management in Pytest is crucial for efficient test execution. Here’s why they matter:
Enhance Consistency: Creating a consistent environment reduces test flakiness caused by inconsistent setup or teardown operations.
Simplify Resource Management:
pytest_sessionstart
andpytest_sessionfinish
centralize setup and teardown logic, managing global resources for the entire test session effectively.Save Time and Money: Pre-check critical resources before the test session to avoid errors from unexpected issues like connection failures or invalid API responses, saving executing time and money.
Optimize Resource Utilization: Initialize essential resources like databases or web servers only once per test session, minimizing the overhead of repetitive setup and teardown.
By leveraging these hooks, you ensure a reliable, efficient, and maintainable testing process.
Project Setup
Let’s check out how to use the pytest_sessionstart
and pytest_sessionfinish
hooks in a practical example.
Prerequisites
- Python (3.12)
- Pytest
Getting Started
Clone the repository containing the example code or if you prefer to start from scratch you can do so by following the similar directory structure.1
2
3
4
5
6
7
8
9
10├── .gitignore
├── README.md
├── poetry.lock
├── pyproject.toml
├── tests
| ├── conftest.py
│ └── test_leap_year.py
└── src
└── leap_year.py
We’ll be using Poetry to manage our dependencies and virtual environment so if you’re not familiar with Poetry I encourage you to start using it.
It’s a great tool that picks the best from pip
, venv
, conda
, and others.
How to Use Pytest Session Management Hooks
Example Code
Our example features a simple leap year checker that uses the Digidates API. Let’s see how it works:
src/leap_year.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49import requests
from requests.exceptions import RequestException
class LeapYear:
"""
Class to manage API calls and check leap years.
Args:
link (str): The API endpoint link.
"""
def __init__(self, endpoint: str):
self.endpoint = endpoint
def check_connection(self) -> int:
"""
Checks the API connection.
Returns:
int: The status code of the API response.
"""
try:
response = requests.get(self.endpoint)
response.raise_for_status() # Raise an HTTPError for bad responses
return response.status_code
except RequestException as e:
print(f"Error checking connection: {e}")
return None
def check_leap_year(self, year: str) -> bool:
"""
Gets the API response data to check leap year.
Returns:
str: The content of the API response.
"""
try:
response = requests.get(f"{self.endpoint}/?year={year}")
response.raise_for_status() # Raise an HTTPError for bad responses
if response.status_code == 200 and response.json()["leapyear"] is True:
return True
elif response.status_code == 200 and response.json()["leapyear"] is False:
return False
else:
return response.json()
except RequestException as e:
print(f"Error fetching data: {e}")
return False
The LeapYear
class initializes with an endpoint URL. The class includes the following methods:
check_connection
: Checks the API connection and returns either the status code orNone
.check_leap_year
: Receives the API response to check if a year is a leap year, returning JSON or a boolean.
Test Code
Here are some simple tests for our example code:
tests/test_leap_year.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import pytest
from src.leap_year import LeapYear
def leap_year():
return LeapYear("https://digidates.de/api/v1/leapyear")
def test_leap_year_1(leap_year):
response = leap_year.check_leap_year(year=2024)
assert response is True
def test_leap_year_2(leap_year):
response = leap_year.check_leap_year(year=2023)
assert response is False
Here, we have a fixture leap_year()
that returns an instance of the LeapYear
class with an API endpoint URL.
We also have two tests, test_leap_year_1
and test_leap_year_2
, each using different years.
In a production setting you’d want various exceptions and failures too.
Add Pytest Sessionstart
To get started, we need to create a conftest.py
file to implement the pytest_sessionstart
hook.
As we’ve learned, Pytest triggers the pytest_sessionstart
hook after the session is created but before test collection and execution begins.
In our case, we’ll use this hook to perform pre-checks, such as verifying the API connection, to ensure everything is set up correctly before running our tests.
Let’s see how we can do that!
tests/conftest.py
1
2
3
4
5
6
7
8
9
10
11
12import pytest
from src.leap_year import LeapYear
def pytest_sessionstart(session):
leap_year = LeapYear("https://digidates.de/api/v1/leapyear")
if leap_year.check_connection() == 200:
print("API connection successful!")
else:
pytest.exit("API Connection Unsuccessful!")
Notice that the hook implementation is done with the decorator @pytest.hookimpl()
.
We created an instance of the LeapYear
class and called the check_connection()
method to evaluate the response status code.
Add Pytest Sessionfinish
Just like pytest_sessionstart
, we’ll use the @pytest.hookimpl()
decorator to mark our pytest_sessionfinish
hook.
The pytest_sessionfinish
hook executes a code block after the test session is complete. In our case, we’ll simply write a message indicating that the tests have finished successfully.
But you could take this to any level you want, like sending a Slack message, generating a report, or cleaning up resources.
tests/conftest.py
1
2
3
def pytest_sessionfinish(session):
print("\nTest session finished!")
Running the Test
Let’s quickly take a look at what a successful test execution looks like.1
pytest -v
Here, we start with a pre-session message indicating that the API connection is ready for the test. Once that’s confirmed, the test collection and execution begin.
Now, let’s see what happens during a failed session. We purposefully closed the network to simulate a network error.
Notice that the pre-session message indicates that the API connection has failed, automatically stopping the test collection and execution.
Very handy, right?
Passing CLI Arguments to Pytest Sessionstart
A complex codebase can operate in multiple modes or require specific input fields. We manage this by providing users with options known as CLI arguments or flags.
How do you pass these CLI arguments or flags in the Pytest sessionstart hook?
You might already be familiar with the Pytest addoption feature, which allows you to pass custom CLI arguments to your test. We’ll be combining this with the Pytest sessionstart hook.
Check out this brief guide on Pytest addoption if you need a refresher.
Here’s how we’re going to use Pytest addoption with the Pytest sessionstart hook:
tests/conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14import pytest
from src.leap_year import LeapYear
def pytest_addoption(parser):
parser.addoption("--api_url", action="store", default="https://digidates.de/api/v1/leapyear", help="Provide API url")
def pytest_sessionstart(session):
leap_year = LeapYear(session.config.getoption("--api_url"))
if leap_year.check_connection() == 200:
print("API connection successful!")
else:
pytest.exit("API Connection Unsuccessful!")
We included a custom CLI argument --api_url
using the pytest_addoption
feature.
Instead of using the request fixture to get the custom option, we used the session
fixture to retrieve the custom argument inside the pytest_sessionstart
hook.
Let’s run the test with the custom argument:1
pytest -v --api_url='https://digidates.de/api/v1/leapyear'
Best Practices for Session Management in Pytest
Let’s quickly dive into some best practices for good session management:
Use Session-Scoped Fixtures Wisely:
- Ideal for resources like database connections, external services, and web servers that are costly to set up and tear down.
- Ensure these fixtures are only for resources that need to be shared across the entire test session and maintain test isolation and randomness.
Use Pytest Sessionstart and Configure Wisely:
pytest_sessionstart
: For session-level setups like initializing databases or starting services, especially for complex setups or conditional checks based on the test environment.pytest_configure
: For setting up or modifying global settings, initializing plugins, and customizing hooks and commands.
Optimize Resource Cleanup:
- Avoid resource leaks by cleaning up properly after tests.
- Use the
yield
statement in fixtures to clearly separate setup and teardown.
By following these practices, you can ensure efficient and reliable session management in your testing process.
Wrapping Up
That’s all on this topic.
In this article, we delved into the powerful Pytest session management hooks: pytest_sessionstart
and pytest_sessionfinish
.
We explored these hooks in detail, uncovering their benefits and practical applications through hands-on examples.
You learned how to add custom options to the pytest_sessionstart
hook, enhancing your ability to tailor the testing process to your specific needs.
Additionally, we shared valuable best practices on session management, ensuring your tests run smoothly and efficiently.
The Pytest session management hooks empower you to execute pre-session tasks, setting the stage for improved test execution.
As you move forward, I encourage you to experiment with these techniques and integrate them into your workflow for even greater testing success.
If you have questions, suggestions, or topics you’d like me to cover in future articles, feel free to reach out via Twitter, GitHub, or Email.
Until next time, happy testing, and may your code always perform as intended! Cheers! 🚀
Additional Reading
Example Code Repository
What is Setup and Teardown in Pytest? (Importance of a Clean Test Environment)
What Is The pytest_configure Hook? (A Simple Guide)
A Simple Guide to Fixing The ‘Pytest Collected 0 Items’” Error
How To Use Pytest With Command Line Options (Easy To Follow Guide)
How To Run Pytest With Poetry (A Step-by-Step Guide)
Introduction To Pytest Hooks (A Practical Guide For Beginners)
What Are Pytest Fixture Scopes? (How To Choose The Best Scope For Your Test)
A Simple Guide to Fixing The ‘Pytest Collected 0 Items’” Error
Pytest API Reference