Pytest Framework in Python

Pytest is a popular testing framework in Python that allows for simple unit tests as well as complex functional testing. It follows a no-boilerplate philosophy, which makes your test code concise and easy to read. With pytest, you can ensure the code behaves as expected and make code changes confidently.

Key Features:

  • Simple to start: Write simple Python functions with assert statements to create tests.
  • Powerful and Extensible: Supports fixtures, parameterized testing, and has numerous plugins.
  • Detailed Info on Failures: Provides detailed information on failures that help quickly introspect and fix issues.
  • Modularity: Write modular tests with fixtures.
  • Skip and xfail: Mark some tests to skip or to be expected to fail.

Basic Usage:

  1. Installation
    Install pytest using pip:
   pip install pytest
  1. Writing a Basic Test
    Tests are simple Python functions that use assert statements.
   def test_addition():
       assert 1 + 1 == 2
  1. Running the Test
    Run tests in a file.
   pytest test_file_name.py

Or run all tests found in a directory.

   pytest testing_directory/

Fixtures for Setup Code:

pytest uses fixtures for setup code. Fixtures are functions that create data or context needed for tests.

import pytest

@pytest.fixture
def sample_dict():
    return {"Python": 3, "pytest": 4}

def test_python_version(sample_dict):
    assert sample_dict["Python"] == 3

Here, sample_dict is a fixture that returns a dictionary. It’s used as an argument in the test function to inject the sample dictionary into the test.

Parameterized Testing:

You can also parameterize test functions, running them multiple times with different arguments.

import pytest

@pytest.mark.parametrize("a,b,expected", [(1, 2, 3), (4, 5, 9), (10, 20, 30)])
def test_addition(a, b, expected):
    assert a + b == expected

Plugins and Configurations:

  • pytest allows you to use plugins and configure your testing regime.
  • Common plugins include pytest-django for Django applications, pytest-asyncio for testing asyncio code, pytest-cov for coverage reporting, and many more.
  • Customize pytest using pytest.ini or pyproject.toml to add configuration settings and default markers.

Skipping and Expected Failure:

  • Use @pytest.mark.skip or @pytest.mark.skipif to skip certain tests.
  • @pytest.mark.xfail can mark tests as expected to fail.

Test Discovery:

  • By default, pytest identifies all files that match the pattern test_*.py or *_test.py as test files.
  • Within test files, any function prefixed with test_ is considered a test.

Example Test File:

import pytest

# A sample fixture
@pytest.fixture
def sample_value():
    return 42

# A basic test
def test_sample_value(sample_value):
    assert sample_value == 42

# Parameterized test
@pytest.mark.parametrize("a,b,expected", [(1, 1, 2), (2, 3, 5)])
def test_addition(a, b, expected):
    assert a + b == expected

# Skipping a test
@pytest.mark.skip(reason="Skip this test.")
def test_skip_example():
    assert 3 == 4

# Expected failure
@pytest.mark.xfail
def test_xfail_example():
    assert 4 == 5

.

Certainly! Below are examples of some Python code along with respective pytest tests.

Example 1: Simple Function Test

Function to Test

# my_sum.py

def my_sum(a, b):
    return a + b

Test

# test_my_sum.py

from my_sum import my_sum

def test_my_sum():
    assert my_sum(2, 3) == 5
    assert my_sum(1, 1) == 2

Run Test:

pytest test_my_sum.py

Example 2: Testing a Class

Class to Test

# person.py

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def get_older(self, years):
        self.age += years

Test

# test_person.py

from person import Person

def test_person_get_older():
    p = Person("Alex", 30)
    p.get_older(5)
    assert p.age == 35

Run Test:

pytest test_person.py

Example 3: Fixture Use

Function to Test

# my_multiply.py

def my_multiply(a, b):
    return a * b

Test

# test_my_multiply.py

import pytest
from my_multiply import my_multiply

@pytest.fixture
def sample_values():
    return 10, 5

def test_my_multiply(sample_values):
    a, b = sample_values
    assert my_multiply(a, b) == 50

Run Test:

pytest test_my_multiply.py

Example 4: Parameterized Test

Function to Test

# my_divide.py

def my_divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero!")
    return a / b

Test

# test_my_divide.py

import pytest
from my_divide import my_divide

@pytest.mark.parametrize("a,b,result", [(10, 2, 5), (20, 4, 5), (30, 3, 10)])
def test_my_divide(a, b, result):
    assert my_divide(a, b) == result

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero!"):
        my_divide(10, 0)

Run Test:

pytest test_my_divide.py

These are basic examples of how to structure and create tests using pytest. It’s an illustrative guide and in real projects, the functions and class methods might be more complex and might require more sophisticated testing mechanisms.

Conclusion:

pytest offers a robust and comprehensive testing solution for Python developers. Its simplicity, extensibility, detailed failure reports, and a plethora of plugins make it an excellent choice for projects of all sizes. Make sure to explore the pytest documentation to dive deeper into its powerful features

Leave a comment