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 valuesassertNotEqual(a, b)- checks the inequalityassertTrue(x)- verifies the validity of the conditionassertFalse(x)- checks if the condition is falseassertIs(a, b)- verifies the identity of objectsassertIsNone(x)- checks for NoneAssert(a, b)- checks the occurrence of an elementassertRaises(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
- Naming: use descriptive names for tests
- Isolation: each test must be independent
- One test, one check: focus on specific functionality
- Data preparation: use fixtures to create test data
- 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.