Python testing using the Unittest and Pytest modules: Creation and execution of tests for checking code performance.

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

A self-study guide for Python 3 compiled from the materials on this site. Primarily intended for those who want to learn the Python programming language from scratch.

Unittest - Python standard library for testing

Unittest is a library built into Python for creating and executing unit tests. It provides a complete set of tools for writing structured tests, organizing checks, and managing test data.

Basic concepts of unittest

TestCase is the base class for creating tests. All test classes must be inherited from unittest.TestCase.

setUp() and tearDown() are special methods that are automatically executed before and after each test. They are used to prepare test data and clean it up.

Assertions are methods for checking the conditions and results of code execution. Include assertEqual(), assertTrue(), assertFalse(), assertRaises() and others.

Practical example of unittest

import unittest

def add(x, y):
"""The function of adding two numbers"""
return x + y

def divide(x, y):
    """Zero-checked division function"""
if y == 0:
raise ValueError("Division by zero is impossible")
return x / y

class TestMathFunctions(unittest.TestCase):
    def setUp(self):
"""Preparing data before each test"""
self.test_value = 42
        self.test_list = [1, 2, 3, 4, 5]

    def tearDown(self):
"""Clearing data after each test"""
del self.test_value
        del self.test_list

    def test_addition_positive(self):
"""Positive addition test"""
result = add(5, 3)
self.assertEqual(result, 8)

    def test_addition_negative(self):
"""Negative number addition test"""
result = add(-2, -3)
self.assertEqual(result, -5)

    def test_setup_value(self):
"""Checking the value from setUp"""
self.assertEqual(self.test_value, 42)
self.assertIsInstance(self.test_value, int)

    def test_list_operations(self):
"""List operations test"""
self.assertIn(3, self.test_list)
self.assertEqual(len(self.test_list), 5)
self.assertTrue(all(isinstance(x, int) for x in self.test_list))

    def test_division_by_zero(self):
"""Exclusion test when dividing by zero"""
with self.assertRaises(ValueError):
divide(10, 0)

    def test_division_normal(self):
"""Normal division test"""
result = divide(10, 2)
self.assertEqual(result, 5.0)

if __name__ == '__main__':
    unittest.main()

The main methods of assertions in unittest

  • assertEqual(a, b) - checks the equality of values
  • assertNotEqual(a, b) - checks the inequality
  • assertTrue(x) - verifies the validity of the condition
  • assertFalse(x) - checks if the condition is false
  • assertIs(a, b) - verifies the identity of objects
  • assertIsNone(x) - checks for None
  • Assert(a, b) - checks the occurrence of an element
  • assertRaises(exc, fun, *args) - checks for an exception

Pytest is a modern testing framework

Pytest is a powerful and flexible framework for testing Python applications. It features a simple syntax, rich functionality, and an extensive ecosystem of plugins.

Installing pytest

pip install pytest

The basics of pytest

# test_math.py
def add(x, y):
"""Addition function"""
    return x + y

def multiply(x, y):
"""Multiplication function"""
    return x * y

def test_add_positive():
"""Positive addition test"""
assert add(2, 3) == 5

def test_add_negative():
"""Negative number addition test"""
assert add(-1, -1) == -2

def test_add_zero():
"""Addition test with zero"""
assert add(5, 0) == 5

def test_multiply():
"""Multiplication test"""
assert multiply(3, 4) == 12
    assert multiply(-2, 3) == -6

Fixtures in pytest

Fixtures allow you to prepare data and resources for tests, as well as manage their lifecycle.

 

import pytest

@pytest.fixture
def sample_data():
"""Fixture with test data"""
    return {"name": "Python", "version": "3.9", "type": "language"}

@pytest.fixture
def temp_file():
"""Temporary file creation fixture"""
import tempfile
    import os
    
    # Create a temporary file
    fd, path = tempfile.mkstemp()
    yield path
    
    # Cleaning after the test
    os.close(fd)
    os.unlink(path)

@pytest.fixture(scope="session")
def database_connection():
"""Session-level fixture for connecting to the database"""
# Connecting to the database
    connection = "fake_db_connection"
    yield connection
    # Closing the connection
    print("Closing the database connection")

def test_data_access(sample_data):
"""Test using a fixture"""
    assert sample_data["name"] == "Python"
    assert sample_data["version"] == "3.9"

def test_file_operations(temp_file):
"""File operation test"""
with open(temp_file, 'w') as f:
        f.write("test content")
    
    with open(temp_file, 'r') as f:
        content = f.read()
        assert content == "test content"

Parameterization of tests

import pytest

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
    (10, -5, 5),
])
def test_add_parametrized(a, b, expected):
    """Parameterized addition test"""
assert add(a, b) == expected

@pytest.mark.parametrize("value,expected", [
    ("hello", True),
    ("", False),
    ("python", True),
    (None, False),
])
def test_string_validation(value, expected):
    """String validation test"""
def is_valid_string(s):
return bool(s and isinstance(s, str))
    
    assert is_valid_string(value) == expected

Labeling of tests

import pytest

@pytest.mark.slow
def test_slow_operation():
"""Slow test"""
import time
    time.sleep(2)
    assert True

@pytest.mark.fast
def test_fast_operation():
"""Fast test"""
assert 1 + 1 == 2

@pytest.mark.integration
def test_database_integration():
"""Integration test"""
assert True

@pytest.mark.unit
def test_unit_function():
"""Unit test"""
assert len("test") == 4

# Running marker tests:
# pytest -m fast # fast tests only
# pytest -m slow # slow tests only
# pytest -m "not slow" # everything except slow

Popular pytest plugins

# pytest-cov - measurement of code coverage
# Installation: pip install pytest-cov
# Launch: pytest --cov=my_module tests/

# pytest-html - HTML reports
# Installation: pip install pytest-html
# Launch: pytest --html=report.html

# pytest-xdist - parallel execution
# Installation: pip install pytest-xdist
# Running: pytest -n 4 # 4 parallel processes

Advanced features of pytest

import pytest

class TestAdvancedFeatures:
    """A class to demonstrate advanced features"""
    
    def test_approximate_comparison(self):
"""Approximate comparison test"""
assert 0.1 + 0.2 == pytest.approx(0.3)
assert [1.0, 2.0, 3.0] == pytest.approx([1.01, 2.01, 3.01], abs=0.1)
    
    def test_exception_handling(self):
"""Exception handling test"""
with pytest.raises(ValueError, match="invalid literal"):
int("not_a_number")
    
    def test_warnings(self):
"""Warnings test"""
import warnings
        with pytest.warns(UserWarning):
            warnings.warn("This is a warning", UserWarning)
    
    @pytest.mark.skip(reason="Feature not implemented yet")
def test_not_implemented(self):
"""Missed test"""
pass
@pytest.mark.skipif(sys.version_info < (3, 8), reason="Requires Python 3.8+")
def test_python_version_specific(self):
"""Test for a specific Python version"""
assert True

Comparison of unittest and pytest

Advantages of unittest

  • Built-in: included in the standard Python library, does not require installation
  • Stability: long development history and wide support
  • Structured: clear organization of tests into classes and methods
  • Compatibility: works in all Python environments without additional dependencies

Advantages of pytest

  • Simplicity of syntax: minimal code for writing tests
  • Powerful fixtures: flexible data preparation system
  • Rich ecosystem: many plug-ins to extend functionality
  • Parameterization: easy creation of multiple tests
  • Informative messages: detailed reports on failed tests
  • Automatic detection: finds and runs tests without additional configuration

When to use unittest

  • Projects where minimizing dependencies is important
  • Corporate environments with restrictions on external libraries
  • Teams that prefer an object-oriented approach to testing
  • Projects that require a complex hierarchy of tests

When to use pytest

  • New projects where you can choose tools
  • Teams that value simplicity and speed of development
  • Projects requiring complex parameterization of tests
  • Cases where specialized capabilities are needed (code coverage, parallel execution)

Best Testing Practices

Project structure

project/
├── src/
│   └── my_module/
│       └── __init__.py
├── tests/
,──__
init__.py ,──
test_unit.py ,─
─ test_integration.py ,── conftest.py # for pytest fixtures
├── requirements.txt
└── setup.py

Recommendations for writing tests

  1. Naming: use descriptive names for tests
  2. Isolation: each test must be independent
  3. One test, one check: focus on specific functionality
  4. Data preparation: use fixtures to create test data
  5. Code coverage: aim for high coverage, but don't forget about the quality of the tests

The choice between unittest and pytest depends on the requirements of the project, the preferences of the team, and the complexity of the code being tested. Both frameworks provide reliable testing of Python applications and have their own unique advantages.

 

 

categories

  • Introduction to Python
  • Python Programming Basics
  • Control Structures
  • Data Structures
  • Functions and Modules
  • Exception Handling
  • Working with Files and Streams
  • File System
  • Object-Oriented Programming (OOP)
  • Regular Expressions
  • Additional Topics
  • General Python Base