0% found this document useful (0 votes)
122 views24 pages

Pytest Framework Essentials

Pytest is a flexible Python testing framework that simplifies the process of writing and running tests, supporting both unit and functional testing. Key features include easy syntax, powerful fixtures, parameterized testing, and extensive plugin support. The document also covers installation, grouping tests, using predefined markers, and the concept of fixtures for test setup and teardown.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
122 views24 pages

Pytest Framework Essentials

Pytest is a flexible Python testing framework that simplifies the process of writing and running tests, supporting both unit and functional testing. Key features include easy syntax, powerful fixtures, parameterized testing, and extensive plugin support. The document also covers installation, grouping tests, using predefined markers, and the concept of fixtures for test setup and teardown.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Pytest Framework Overview

Pytest is a popular Python testing framework known for its simplicity and flexibility. It is widely
used for writing and running test cases because it supports both simple unit tests and complex
functional testing. It provides features like fixture management, parameterized testing, and
support for plugins.

Key Features of Pytest

1. Ease of Use: Simple syntax for writing tests.


2. Fixtures: Powerful dependency injection for test data or setup/teardown logic.
3. Parameterized Testing: Simplifies writing tests with multiple sets of inputs.
4. Assertions: Enhanced assertion introspection for better debugging.
5. Plugins: Extensible with a rich set of plugins.
6. Test Discovery: Automatically finds tests based on the test_ prefix.

Installing Pytest

To install pytest, use pip:

pip install pytest

1. Basic Unit Test

# test_basic.py
def multiply(a, b):
return a * b

def test_multiply():
assert multiply(2, 3) == 6
assert multiply(0, 5) == 0
assert multiply(-1, 4) == -4

2. Testing Exceptions
|-[Link] is a powerful context manager in pytest used to test a
specific exception is raised during the execution of code.

Syntax:

import pytest

with [Link](ExpectedException):
#code that should raise the exception

# test_exceptions.py
def divide(a, b):
if b == 0:
raise ValueError("Division by zero is not allowed")
return a / b

def test_divide():
with [Link](ValueError, match="Division by zero is not
allowed"):
divide(1, 0)

Grouping Tests in Pytest

In pytest, you can group tests to organize your test suite better and improve maintainability.
Tests can be grouped by files, functions, classes, or using markers.

1. Grouping by Test Files

Simply organize tests into different files for logical separation.

File Structure:
tests/
├── test_math_operations.py
├── test_string_operations.py

File: test_math_operations.py
def test_add():
assert 1 + 1 == 2

def test_subtract():
assert 5 - 3 == 2

File: test_string_operations.py
def test_uppercase():
assert "hello".upper() == "HELLO"

def test_split():
assert "hello world".split() == ["hello", "world"]

Run all tests:

pytest

Run a specific file:

pytest tests/test_math_operations.py

2. Grouping by Test Classes

You can use classes to group related tests logically. Classes should not have an __init__
method when used for pytest tests.

File: test_group_classes.py

class TestMathOperations:
def test_add(self):
assert 2 + 3 == 5

def test_subtract(self):
assert 7 - 4 == 3

class TestStringOperations:
def test_uppercase(self):
assert "pytest".upper() == "PYTEST"

def test_split(self):
assert "a b c".split() == ["a", "b", "c"]
Run all tests in a class:

pytest test_group_classes.py::TestMathOperations

3. Grouping by Markers

Markers allow you to label and run specific groups of tests.

File: test_group_markers.py

import pytest

@[Link]
def test_add():
assert 1 + 1 == 2

@[Link]
def test_multiply():
assert 2 * 3 == 6

@[Link]
def test_uppercase():
assert "hello".upper() == "HELLO"

@[Link]
def test_split():
assert "hello world".split() == ["hello", "world"]

Run only math tests:

pytest -m math

Run only string tests:

pytest -m string

Note: To register custom markers, add them to [Link]:

# [Link]
[pytest]
markers =
math: Tests related to mathematical operations
string: Tests related to string operations

4. Grouping with Test Classes and Fixtures

Combine classes and fixtures for better setup and teardown management.

File: test_classes_fixtures.py
import pytest

@[Link]
def setup_data():
return {"key": "value"}

class TestAPI:
def test_get_data(self, setup_data):
assert setup_data["key"] == "value"

def test_post_data(self, setup_data):


setup_data["new_key"] = "new_value"
assert "new_key" in setup_data

5. Grouping by Directory Hierarchy

Use directories to group related test files. For example:

tests/
├── math/
│ ├── test_addition.py
│ ├── test_subtraction.py
├── string/
│ ├── test_uppercase.py
│ ├── test_split.py

Run tests in a specific directory:

pytest tests/math/
Summary of Commands for Grouping

 Run all tests:

pytest

 Run tests in a specific file:

pytest test_math_operations.py

 Run tests in a specific class:

pytest test_group_classes.py::TestMathOperations

 Run tests by marker:

pytest -m math

 Run tests in a specific directory:

pytest tests/math/

Predefined Markers in Pytest

Pytest comes with several predefined markers to provide functionality like skipping tests,
expecting tests to fail, or setting conditional execution. These markers are built into pytest and
help handle various testing scenarios.

Common Predefined Markers

1. @[Link]
Skip a test unconditionally.

import pytest
@[Link](reason="This test is not implemented yet")
def test_skip_example():
assert 1 == 2
Run:

pytest -v

Output:

swift
SKIPPED [1] test_skip.py: This test is not implemented yet

2. @[Link]
Skip a test conditionally based on a condition.

import pytest
import sys

@[Link](sys.version_info < (3, 8), reason="Requires Python


3.8 or higher")
def test_skipif_example():
assert 1 == 1

Run on Python < 3.8:

SKIPPED [1] test_skipif.py: Requires Python 3.8 or higher

3. @[Link]
Mark a test as expected to fail.

import pytest

@[Link](reason="Known bug, fixing in next release")


def test_xfail_example():
assert 1 == 2

Output:

arduino
XFAIL test_xfail.py::test_xfail_example
If the test passes unexpectedly:

XPASS test_xfail.py::test_xfail_example

4. @[Link]
Run the same test with multiple sets of parameters.

import pytest

@[Link]("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-1, -1, -2)
])
def test_parametrize_example(a, b, expected):
assert a + b == expected

Output:

test_parametrize.py::test_parametrize_example[1-2-3] PASSED
test_parametrize.py::test_parametrize_example[0-0-0] PASSED
test_parametrize.py::test_parametrize_example[-1--1--2] PASSED

5. @[Link]
Apply a fixture to a test function or class.

import pytest

@[Link]
def setup_data():
return {"key": "value"}

@[Link]("setup_data")
def test_fixture_example(setup_data):
assert setup_data["key"] == "value"
6. @[Link]
Control warnings during test execution.

import warnings
import pytest

@[Link]("ignore:.*deprecated.*")
def test_warnings():
[Link]("This is a deprecated feature",
DeprecationWarning)
assert True

Running Tests with Predefined Markers

To List All Available Markers


pytest --markers

Example Output:

@[Link](reason): skip the test with an optional reason


@[Link](condition, reason): skip the test if condition is True
@[Link](condition, reason, ...): mark the test as expected to fail

Additional Options for Predefined Markers

 Run tests marked as xfail only:

pytest -rx

 Show reasons for skipped tests:

pytest -rs

 Combine markers:

@[Link](condition, reason="reason")
@[Link](reason="reason")
def test_combined_marker():
assert False

3. Using Fixtures for Test Setup


# test_fixtures.py
import pytest

@[Link]
def sample_user():
return {"username": "test_user", "email": "test@[Link]"}

def test_user_email(sample_user):
assert sample_user["email"] == "test@[Link]"

What is a Fixture in Pytest?

A fixture in pytest is a function used to provide a fixed baseline for tests. Fixtures allow you to
set up prerequisites or shared resources needed for your tests, such as test data, database
connections, or configurations. They can be reused across multiple test functions and provide a
way to manage setup and teardown logic efficiently.

Key Features of Fixtures

1. Reusable: Defined once and used across multiple tests.


2. Flexible Scope: Can be applied at different levels (function, class, module, or session).
3. Dependency Injection: Automatically passed to the tests that need them.
4. Automatic Teardown: Cleanup logic can be implemented for resources after tests run.

Creating and Using Fixtures

import pytest

#Define a fixture
@[Link]
def set_up():
print("Test Case execution started")
def test_t1(set_up):
print("This is test1")

def test_t2(set_up):
print("This is test2")

def test_t3(set_up):
print("This is test3")

def test_t4(set_up):
print("This is test4")

def test_t5(set_up):
print("This is test5")

import pytest

@[Link]
def sample_data():
return [1,2,3,4,5]

def test_sum(sample_data):
print(sample_data)
assert sum(sample_data)==15

import pytest

@[Link]
def sample_list():
return [1,2,3]

#Test that uses the fixture


def test_list_length(sample_list):
assert len(sample_list)==3

def test_list_sum(sample_list):
assert sum(sample_list)==6

import pytest

@[Link]
def setup():
print("Test Case Execution started!!")
yield
print("Test case Execution Completed!!")

#setup logic : the logic needs to be execute before test case


#teardown logic : the logic needs to be execute after test
case

def test_sample(setup):
print("Test: Sample test")

Example: Basic Fixture


import pytest

@[Link]
def sample_data():
return {"key": "value"}

def test_sample_data(sample_data):
assert sample_data["key"] == "value"
 The sample_data fixture provides a dictionary for the test function.
 Pytest automatically passes the sample_data fixture to the test function when requested.

Fixture Scope

The default scope of a fixture is function, meaning the fixture is created and torn down for each
test function. Other scopes include:

1. function: (Default) Fixture runs for each test function.


2. class: Fixture runs once per test class.
3. module: Fixture runs once per module.
4. session: Fixture runs once for the entire test session.

[Link] Scope(Default)
|-Fixture is created and tear down for each test function

test_function_scope.py

import pytest

@[Link]
def sample_data():
print("\n[Setup] Creating sample data")
yield [1,2,3]
print("[Teardown] Cleaning sample data")

def test_sum(sample_data):
print(f"sum= {sum(sample_data)}")
assert sum(sample_data)==6

def test_length(sample_data):
print(f"Length= {len(sample_data)}")
assert len(sample_data)==3
Test_class_scope.py

import pytest

#Fixture is created once for the test class, and resued for
all its method
@[Link](scope="class")
def class_data():
print("\n[Setup] Class-level data")
yield {"name":"pytest"}
print("[Teardown] Class-level data")

class TestClassExample:

def test_name(self,class_data):
print("this is test_name")

def test_uppercase(self,class_data):
print("this is test_uppercase")

Example: Fixture with Different Scopes


import pytest

@[Link](scope="module")
def setup_once():
return "Module Level Setup"

def test_1(setup_once):
assert setup_once == "Module Level Setup"

def test_2(setup_once):
assert setup_once == "Module Level Setup"

Using Fixtures for Setup and Teardown

Fixtures can include setup and teardown logic using yield.

Example: Setup and Teardown


import pytest

@[Link]
def resource_setup():
print("Setting up resource")
resource = {"status": "ready"}
yield resource
print("Tearing down resource")

def test_resource(resource_setup):
assert resource_setup["status"] == "ready"

Output:

Setting up resource
Tearing down resource

 The code before yield is executed before the test.


 The code after yield is executed after the test, ensuring proper cleanup.

Parameterizing Fixtures

Fixtures can be parameterized to run tests with different sets of data.

# A parameterized fixture allows a fixture to run multiple


times with different input values.
# @[Link](params=[....])
import pytest

@[Link](params=[1,2,3])
def number(request):
return [Link]

def test_is_even(number):
print(f"Testing with : {number}")
assert number%2==0

@[Link](params=[
(2,4),
(3,9),
(4,16)
])
def number_pair(request):
return [Link]

def test_square(number_pair):
num,expected=number_pair
assert num**2==expected

Example: Parameterized Fixture


import pytest

@[Link](params=[1, 2, 3])
def numbers(request):
return [Link]

def test_numbers(numbers):
assert numbers in [1, 2, 3]

 The test runs three times, once for each value in the params list.
Autouse Fixtures

Fixtures can be set to run automatically for all tests without explicitly requesting them using
autouse=True.

import pytest

#This fixture will be run before and after each test


automatically
@[Link](autouse=True)
def auto_setup_teardown():
print("[Setup] this runs before each test")
yield
print("[Teardown] this runs after each test")

def test_one():
print("Running test_one")
assert 1+1==2

def test_two():
print("Running test_two")
assert "hello".upper()=="HELLO"

Example: Autouse Fixture


import pytest

@[Link](autouse=True)
def always_run():
print("This runs before every test")

def test_example():
assert True
Fixtures with Dependencies

Fixtures can depend on other fixtures, creating a chain of setup logic.

import pytest

@[Link]
def database():
print("Setting up database")
return "my_database"

@[Link]
def user(database):
print("Creating user with DB: ",database)
return f" user_object_using_{database}"

def test_user(user):
print("Ruuning test case with: ",user)
assert "user_object_using" in user

import pytest

@[Link]
def config():
return {"env":"dev"}

@[Link]
def db(config):
return f" {config['env']}_db"
@[Link]
def app(db):
return f"web_app_using_{db}"

def test_app(app):
assert "web_app_using" in app

# test_app(app)-->aap(db)-->db(config)->config

Example: Fixture Dependencies


import pytest

@[Link]
def user_data():
return {"username": "test_user"}

@[Link]
def logged_in_user(user_data):
user_data["logged_in"] = True
return user_data

def test_logged_in_user(logged_in_user):
assert logged_in_user["logged_in"]
assert logged_in_user["username"] == "test_user"

Common Use Cases for Fixtures

1. Database Setup:

@[Link]
def db_connection():
conn = connect_to_database()
yield conn
[Link]()

2. Temporary File/Directory:

@[Link]
def temp_file(tmp_path):
file = tmp_path / "test_file.txt"
file.write_text("Hello, pytest!")
return file

3. API Mocking:

@[Link]
def mock_api(mocker):
mock = [Link]("api_client.get_data")
mock.return_value = {"key": "value"}
return mock

Summary

 Fixtures are essential for managing repetitive setup/teardown tasks in pytest.


 They simplify test logic by providing reusable and clean setups.
 With features like parameterization, scoping, and dependencies, they enable scalable and
maintainable testing.

What is [Link] in Pytest?

[Link] is a special configuration file used in the pytest testing framework. It serves as a
central location for defining fixtures, hooks, and other test configurations that can be shared
across multiple test files in a project. The key advantage is that it allows test files to use these
configurations without explicitly importing them.

Features of [Link]

1. Fixture Sharing: Define reusable fixtures that are automatically discovered by pytest.
2. Directory Scope: The configurations in [Link] apply to all test files in the same
directory and its subdirectories.
3. Test Hooks: Customize pytest behavior using hooks, such as adding command-line
options or modifying the test collection process.
4. No Explicit Import: Test files within the scope of [Link] can use its fixtures and
configurations without needing to import them explicitly.
test_sample.py

def test_ex1(sample_fixture):
print("test_ex1")

test_sample2.py
def test_ex2(sample_fixture):
print("test_ex2")

[Link]
import pytest

@[Link]
def sample_fixture():
print("this is fixture")

Example: Using [Link] for Fixtures

Directory Structure
tests/
├── [Link]
├── test_example.py
├── subdir/
│ ├── [Link]
│ ├── test_sub_example.py
[Link]
import pytest

@[Link]
def sample_data():
return {"key": "value"}

test_example.py
def test_sample_data(sample_data):
assert sample_data["key"] == "value"

subdir/[Link]
import pytest

@[Link]
def subdir_data():
return {"subdir_key": "subdir_value"}

subdir/test_sub_example.py
def test_subdir_data(subdir_data):
assert subdir_data["subdir_key"] == "subdir_value"

Scoping Fixtures in [Link]

Fixtures in [Link] can have a broader scope (module, session, etc.) to avoid being
recreated for every test.

Example: Session Scoped Fixture


# [Link]
import pytest

@[Link](scope="session")
def database_connection():
print("\nSetting up database connection")
conn = {"db": "connected"}
yield conn
print("\nTearing down database connection")

test_database.py
def test_database_connection_1(database_connection):
assert database_connection["db"] == "connected"

def test_database_connection_2(database_connection):
assert database_connection["db"] == "connected"

Output:

Setting up database connection


Tearing down database connection

The fixture is set up only once for the session and reused across tests.

Using Hooks in [Link]

You can define pytest hooks in [Link] to modify or customize pytest’s behavior.

Example: Modifying Command-Line Options


python
Copy code
# [Link]
def pytest_addoption(parser):
[Link]("--env", action="store", default="dev", help="Environment
to run tests against")

@[Link]
def env(pytestconfig):
return [Link]("--env")

test_environment.py
python
Copy code
def test_environment(env):
assert env in ["dev", "staging", "prod"]

Run the test:

bash
Copy code
pytest --env=staging

Using [Link] in Subdirectories

[Link] files in subdirectories override or supplement those in parent directories.

Directory Structure
Copy code
tests/
├── [Link]
├── test_example.py
├── subdir/
│ ├── [Link]
│ ├── test_sub_example.py

 tests/[Link]: Fixtures and hooks for all tests.


 tests/subdir/[Link]: Additional fixtures/hooks only for tests in subdir.

Best Practices for Using [Link]

1. Organize logically:
o Use [Link] for shared fixtures or hooks relevant to a specific directory.
o Avoid putting too many unrelated fixtures in one [Link].

2. Use clear names:


o Name fixtures descriptively to indicate their purpose.

3. Limit scope:
o Use appropriate fixture scopes (function, module, session) to balance test isolation
and performance.

4. Avoid imports from [Link]:


o Fixtures in [Link] should be implicitly available; importing them in test files
defeats the purpose.

You might also like